2. How to use

The main purpose of strong_typing is to help you create C++-like Struct, that is to say classes where the attributes are ALL defined and cannot be of any type. An undefined attribute cannot be added later on.

To do this, we use several objects:
  • strong_typing.Struct which must be overridden
  • strong_typing.typed_parameters.<type>Parameter which will help us to define our attributes’ types

2.1. Create a Struct

Let’s create our first Struct !

# mystruct.py

from strong_typing import Struct

class MyStruct(Struct):
  # It is not very interesting, but as a starter, let's create a
  # Struct without any attribute
  pass
# script.py
from mystruct import MyStruct

my_struct = MyStruct()
my_struct.any_attrib = 10 # raises AttributeError

And that’s it ! You just created your first strongly-typed Struct in Python !! An undefined attribute cannot be added at runtime.

2.2. Add parameters

Creating a Struct without parameters is not really interesting. Let’s add an int and a string to our class.

# mystruct.py

from strong_typing import Struct
from strong_typing.typed_parameters import (IntegerParameter,
                                            StringParameter)

class MyStruct(Struct):
  __ATTRIBUTES__=[IntegerParameter(name="my_int"),
                  StringParameter(name="my_str")]

We have now two attributes available for MyStruct. When someone will try to set them, the given value will be converted into the requested type if possible. If the given value cannot be converted, an error will be raised.

# script.py
from mystruct import MyStruct

my_struct = MyStruct()
my_struct.my_int = 10.0 # is correct (10 will be stored)
my_struct.my_int = "20" # is correct (20 will be stored)
my_struct.my_int = "aa" # will raise an Exception
my_struct.my_str = 10.0 # is correct ("10.0" will be stored)

There is no limit to the number of parameters. Just add as many as you want in the __ATTRIBUTES__ list and you’re good to go.

2.3. Parametrize the parameters

Now that we know how to add parameters, let’s see how much cooler they can make our life.

For this, let’s have a look at the base class of all parameters

class strong_typing.typed_parameters._parameters.ParameterType(name, description, default, id=None)

Base type for parameters

Parameters:
  • name – Name of the parameter
  • description – Description of the parameter
  • default – Default value
  • id – Token used to access the parameter

If default is None, it means the parameter is optional and can have None as a value. Otherwise, setting the parameter to None actually resets it to the default value.

If id is None, it will by default be the name of the parameter turned into a snakecase string. Otherwise, the only requirement is for the id to be a snakecase string (it doesn’t have to be related to the parameter’s name)

Example:If name is ‘My Parameter’, then id will be ‘my_parameter’, and its value will be accessible by calling <owning_class>.my_parameter

This means we can customize the default value of our parameters, but also that we can change the token through which they are accessed (as long as the final token is in snake-case).

# mystruct.py

from strong_typing import Struct
from strong_typing.typed_parameters import IntegerParameter

class MyStruct(Struct):
  __ATTRIBUTES__=[IntegerParameter(name="My integer", id="a", default=10),
                  IntegerParameter(name="My other integer", id="b", default=None)]
# script.py
from mystruct import MyStruct

my_struct = MyStruct()
print my_struct.a # prints 10
print my_struct.b # prints None
my_struct.a = 20
my_struct.a = None
print my_struct.a # prints 10
Question:

Hold on !

In ParameterType signature, there is no default value for the default attribute. But in the first and second examples, we did not define a default value. Why is that ?

Good question ! Let’s examine that !

2.3.1. Parameters for immutable types

In our previous example, the defined parameters had a default value because numeric parameters (int, float, ...) have a default default value.

class strong_typing.typed_parameters.IntegerParameter(name='', description='', default=0, normalizer=None, range=(None, None), id=None)

Bases: strong_typing.typed_parameters._parameters.NumericParameter

Integer parameter

class strong_typing.typed_parameters.FloatParameter(name='', description='', default=0.0, normalizer=None, range=(None, None), id=None)

Bases: strong_typing.typed_parameters._parameters.NumericParameter

Float parameter

You can see that a default value is defined for both integer and float. But you can also notice that new arguments are available. What are they ?

class strong_typing.typed_parameters._parameters.NumericParameter(name, description, default, normalizer, range=(None, None), id=None)

Bases: strong_typing.typed_parameters._parameters.ParameterType

Base type for numeric parameters

Parameters:
  • normalizer – Extra-verification function
  • range – Couple of values representing the lowest and highest possible values the parameter can take It is possible to define only one of the two and leave the other to None to have only a minimum or only a maximum

normalizer is a user-defined function allowing him to perform an extra-verification on the inserted value. For instance, a function can be added to accept only odd numbers, or perfect square number. In case the inserted value does not match the requirement, the function must provide a valid value instead of the inserted one.

normalizer and range allow us to add new constraints on our values. We can define min and max values, but also more specific restriction, like “odd numbers only”

# mystruct.py

from strong_typing import Struct
from strong_typing.typed_parameters import IntegerParameter

def make_odd(x):
  return (x/2)*2+1

class MyStruct(Struct):
  __ATTRIBUTES__=[IntegerParameter(name="norm", normalizer=make_odd),
                  IntegerParameter(name="ranged", range=(10,20))]
# script.py
from mystruct import MyStruct

my_struct = MyStruct()
print my_struct.norm # prints 1
# the default is 0, but as we only accept odd numbers,
# 0 was transformed in 1

print my_struct.range # prints 10
# the default is 0, but as we only accept numbers
# between 10 and 20, 0 became 10

Another parameter that has a default default value is BoolParameter

class strong_typing.typed_parameters.BoolParameter(name='', description='', default=False, id=None)

Bases: strong_typing.typed_parameters._parameters.ParameterType

Nothing major to say about it

The last “standard” immutable available parameter is StringParameter

class strong_typing.typed_parameters.StringParameter(name='', description='', default=None, id=None)

Bases: strong_typing.typed_parameters._parameters.ParameterType

The only notable difference with the previous parameters is its behavior regarding the default value. Unlike the others, a StringParameter does not allow to set a value to None, even if it is the default value selected. A None default value will be transformed in an empty string. Because of that, setting a string to “” is equivalent to setting it to None, ie to resetting it to default value. As a result, if you want to allow your string to be empty, default value MUST be “” or None (same as numeric or bool whose default must be None if you want this value to be allowed).

# mystruct.py

from strong_typing import Struct
from strong_typing.typed_parameters import StringParameter

class MyStruct(Struct):
  __ATTRIBUTES__=[StringParameter(name="str1", default="default"),
                  StringParameter(name="str2")]
# script.py
from mystruct import MyStruct

my_struct = MyStruct()
print my_struct.str1 # prints "default"
my_struct.str1 = "string"
print my_struct.str1 # prints "string"
my_struct.str1 = None # reset to default
print my_struct.str1 # prints "default"
my_struct.str1 = "" # ALSO reset to default
print my_struct.str1 # prints "default"


print my_struct.str2 # prints ""
my_struct.str1 = "string"
print my_struct.str1 # prints "string"
my_struct.str1 = None # reset to default
print my_struct.str1 # prints ""
my_struct.str1 = "" # ALSO reset to default
print my_struct.str1 # prints ""

2.3.2. A special type of immutable: Enum

Like in C++, it is possible to define Enums in our classes. There are several ways to implement Enums in Python, we have chosen to use the library enum which is automatically installed on Python3 (and can be installed via pip on Python2).

class strong_typing.typed_parameters.EnumParameter(choices, name='', description='', default=None, id=None)

Bases: strong_typing.typed_parameters._parameters.ParameterType

Parameter describing a set of choices

Parameters:
  • choices – Set of possible choices. It can be a list of strings, or an enum.Enum
  • default – Default value

If default is None, the first of the available choices is selected as default.

You can see a choices argument in EnumParameter constructor, this is where you will insert your enum. You can create an Enum, but you can also more simply give a list of strings.

Warning:The default value of an EnumParameter cannot be None. If default value is not set, the first value of your list/enum will be used as default.
# mystruct.py

from strong_typing import Struct
from strong_typing.typed_parameters import EnumParameter

import enum

class Options(enum.Enum):
  a = 1
  b = 2

class MyStruct(Struct):
  __ATTRIBUTES__=[EnumParameter(name="choice1", choices=["a", "b"]),
                  EnumParameter(name="choice2", choices=Options, default=Choices.b)]
# script.py
from mystruct import MyStruct

my_struct = MyStruct()
print my_struct.choice1 # prints "a"
my_struct.choice1 = "b"
print my_struct.choice1 # prints "b"
my_struct.choice1 = None # reset to default
print my_struct.choice1 # prints "a"

print my_struct.choice2 # prints "<Choices.a : 1>"
my_struct.choice2 = Choices.b # works
my_struct.choice2 = "b" # also works (but b is converted in Choices.b)

2.3.3. Parameters for mutable types

Now let’s see how are handled the mutable types.

Lists are handled through VectorParameters.

class strong_typing.typed_parameters.VectorParameter(type, name='', description='', default=None, id=None)

Handles list

Parameters:type – Type of the elements stored in the list

You can see a new argument type. It is here to define the type of element the list will contain.

Warning:For now, it is not possible for a list to contain other lists with a defined type. We wish we can add this in a latter version.

As for the strings, it is not possible to have None as a default value. It will be replaced by an empty list. Therefore, if you set your list parameter to None, it will actually be set to []. And if you define a default value (which must be a list), then this list will be used if you set your parameter to None. However, unlike a StringParameter without any default value, it IS possible to set your list to [] even with a non-default value: you can just remove all elements from the list, without assigning it directly a value.

Maps are currently not handled. This will probably be done in a future version

2.3.4. Other Struct as parameters

Finally, it is possible to define a Struct as parameter of another Struct.

class strong_typing.typed_parameters.StructParameter(type, default=None, name='', description='', id=None)

Handles a Struct

Parameters:type – Type of the Struct to be used

type is the type of the Struct you want to use as parameter. If default is left to None a default instance of Struct is used as default.