feat: documentation
32
.readthedocs.yaml
Normal file
|
@ -0,0 +1,32 @@
|
|||
# .readthedocs.yaml
|
||||
# Read the Docs configuration file
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
# Required
|
||||
version: 2
|
||||
|
||||
# Set the OS, Python version and other tools you might need
|
||||
build:
|
||||
os: ubuntu-22.04
|
||||
tools:
|
||||
python: "3.12"
|
||||
# You can also specify other tool versions:
|
||||
# nodejs: "19"
|
||||
# rust: "1.64"
|
||||
# golang: "1.19"
|
||||
|
||||
# Build documentation in the "docs/" directory with Sphinx
|
||||
sphinx:
|
||||
configuration: docs/conf.py
|
||||
|
||||
# Optionally build your docs in additional formats such as PDF and ePub
|
||||
# formats:
|
||||
# - pdf
|
||||
# - epub
|
||||
|
||||
# Optional but recommended, declare the Python requirements required
|
||||
# to build your documentation
|
||||
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
|
||||
python:
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
350
doc/browse.md
|
@ -1,350 +0,0 @@
|
|||
# Browse the Config
|
||||
|
||||
## Getting the options
|
||||
|
||||
Create a simple Config:
|
||||
|
||||
```python
|
||||
from asyncio import run
|
||||
from tiramisu import Config
|
||||
from tiramisu import StrOption, OptionDescription
|
||||
|
||||
# let's declare some options
|
||||
var1 = StrOption('var1', 'first option')
|
||||
# an option with a default value
|
||||
var2 = StrOption('var2', 'second option', 'value')
|
||||
# let's create a group of options
|
||||
od1 = OptionDescription('od1', 'first OD', [var1, var2])
|
||||
|
||||
# let's create another group of options
|
||||
rootod = OptionDescription('rootod', '', [od1])
|
||||
|
||||
async def main():
|
||||
# let's create the config
|
||||
cfg = await Config(rootod)
|
||||
# the api is read only
|
||||
await cfg.property.read_only()
|
||||
# the read_write api is available
|
||||
await cfg.property.read_write()
|
||||
return cfg
|
||||
|
||||
cfg = run(main())
|
||||
```
|
||||
|
||||
We retrieve by path an option named "var1" and then we retrieve its name and its docstring:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
print(await cfg.option('od1.var1').option.name())
|
||||
print(await cfg.option('od1.var1').option.doc())
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
var1
|
||||
first option
|
||||
```
|
||||
|
||||
## Accessing the values of the options
|
||||
|
||||
Let's browse the configuration structure and option values.
|
||||
|
||||
You have getters as a "get" method on option objects:
|
||||
|
||||
1. getting all the options
|
||||
|
||||
```python
|
||||
async def main():
|
||||
print(await cfg.value.dict())
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
{'var1': None, 'var2': 'value'}
|
||||
```
|
||||
|
||||
2. getting the "od1" option description
|
||||
|
||||
```python
|
||||
async def main():
|
||||
print(await cfg.option('od1').value.dict())
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
{'od1.var1': None, 'od1.var2': 'value'}
|
||||
```
|
||||
|
||||
3. getting the var1 option's value
|
||||
|
||||
```python
|
||||
async def main():
|
||||
print(await cfg.option('od1.var1').value.get())
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
None
|
||||
```
|
||||
|
||||
4. getting the var2 option's default value
|
||||
|
||||
```python
|
||||
async def main():
|
||||
print(await cfg.option('od1.var2').value.get())
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
value
|
||||
```
|
||||
|
||||
5. trying to get a non existent option's value
|
||||
|
||||
```python
|
||||
async def main():
|
||||
try:
|
||||
await cfg.option('od1.idontexist').value.get()
|
||||
except AttributeError as err:
|
||||
print(str(err))
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
unknown option "idontexist" in optiondescription "first OD"
|
||||
```
|
||||
|
||||
## Setting the value of an option
|
||||
|
||||
An important part of the setting's configuration consists of setting the
|
||||
value's option.
|
||||
|
||||
You have setters as a "set" method on option objects.
|
||||
|
||||
And if you wanna come back to a default value, use the "reset()" method.
|
||||
|
||||
1. changing the "od1.var1" value
|
||||
|
||||
```python
|
||||
async def main():
|
||||
await cfg.option('od1.var1').value.set('éééé')
|
||||
print(await cfg.option('od1.var1').value.get())
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
éééé
|
||||
```
|
||||
|
||||
2. carefull to the type of the value to be set
|
||||
|
||||
```python
|
||||
async def main():
|
||||
try:
|
||||
await cfg.option('od1.var1').value.set(23454)
|
||||
except ValueError as err:
|
||||
print(str(err))
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
"23454" is an invalid string for "first option"
|
||||
```
|
||||
|
||||
3. let's come back to the default value
|
||||
|
||||
```python
|
||||
async def main():
|
||||
await cfg.option('od1.var2').value.reset()
|
||||
print(await cfg.option('od1.var2').value.get())
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
value
|
||||
```
|
||||
|
||||
> **_Important_** If the config is "read only", setting an option's value isn't allowed, see [property](property.md).
|
||||
|
||||
Let's make the protocol of accessing a Config's option explicit
|
||||
(because explicit is better than implicit):
|
||||
|
||||
1. If the option has not been declared, an "Error" is raised,
|
||||
2. If an option is declared, but neither a value nor a default value has
|
||||
been set, the returned value is "None",
|
||||
3. If an option is declared and a default value has been set, but no value
|
||||
has been set, the returned value is the default value of the option,
|
||||
|
||||
4. If an option is declared, and a value has been set, the returned value is
|
||||
the value of the option.
|
||||
|
||||
But there are special exceptions. We will see later on that an option can be a
|
||||
mandatory option. A mandatory option is an option that must have a value
|
||||
defined.
|
||||
|
||||
Searching for an option
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In an application, knowing the path of an option is not always feasible.
|
||||
That's why a tree of options can easily be searched with the "find()" method.
|
||||
|
||||
```python
|
||||
from asyncio import run
|
||||
from tiramisu import Config
|
||||
from tiramisu import OptionDescription, StrOption
|
||||
from tiramisu.setting import undefined
|
||||
|
||||
var1 = StrOption('var1', '')
|
||||
var2 = StrOption('var2', '')
|
||||
var3 = StrOption('var3', '')
|
||||
od1 = OptionDescription('od1', '', [var1, var2, var3])
|
||||
var4 = StrOption('var4', '')
|
||||
var5 = StrOption('var5', '')
|
||||
var6 = StrOption('var6', '')
|
||||
var7 = StrOption('var1', '', 'value')
|
||||
od2 = OptionDescription('od2', '', [var4, var5, var6, var7])
|
||||
rootod = OptionDescription('rootod', '', [od1, od2])
|
||||
```
|
||||
|
||||
Let's find an option by it's name
|
||||
|
||||
And let's find first an option by it's name
|
||||
|
||||
The search can be performed in a subtree
|
||||
|
||||
```python
|
||||
async def main():
|
||||
cfg = await Config(rootod)
|
||||
print(await cfg.option.find(name='var1'))
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
[<tiramisu.api.TiramisuOption object at 0x7f490a530f98>, <tiramisu.api.TiramisuOption object at 0x7f490a530748>]
|
||||
```
|
||||
|
||||
If the option name is unique, the search can be stopped once one matched option has been found:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
cfg = await Config(rootod)
|
||||
print(await cfg.option.find(name='var1', first=True))
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
<tiramisu.api.TiramisuOption object at 0x7ff27fc93c70>
|
||||
```
|
||||
|
||||
Search object behaves like a cfg object, for example:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
cfg = await Config(rootod)
|
||||
option = await cfg.option.find(name='var1', first=True)
|
||||
print(await option.option.name())
|
||||
print(await option.option.doc())
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
var1
|
||||
var1
|
||||
```
|
||||
|
||||
Search can be made with various criteria:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
cfg = await Config(rootod)
|
||||
await cfg.option.find(name='var3', value=undefined)
|
||||
await cfg.option.find(name='var3', type=StrOption)
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
The find method can be used in subconfigs:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
cfg = await Config(rootod)
|
||||
print(await cfg.option('od2').find('var1'))
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
## The "dict" flattening utility
|
||||
|
||||
In a config or a subconfig, you can print a dict-like representation
|
||||
|
||||
In a "fullpath" or a "flatten" way
|
||||
|
||||
- get the "od1" option description:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
cfg = await Config(rootod)
|
||||
print(await cfg.option('od1').value.dict(fullpath=True))
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
{'od1.var1': None, 'od1.var2': None, 'od1.var3': None}
|
||||
```
|
||||
|
||||
```python
|
||||
async def main():
|
||||
cfg = await Config(rootod)
|
||||
print(await cfg.option('od1').value.dict(fullpath=False))
|
||||
|
||||
run(main())
|
||||
>>> print(cfg.option('od1').value.dict(fullpath=True))
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
{'var1': None, 'var2': None, 'var3': None}
|
||||
```
|
||||
|
||||
> **_NOTE_** be carefull with this "flatten" parameter, because we can just loose some options if there are same name (some option can be overriden).
|
||||
|
107
doc/config.md
|
@ -1,107 +0,0 @@
|
|||
# The Config
|
||||
|
||||
Tiramisu is made of almost three main classes/concepts :
|
||||
|
||||
- the "Option" stands for the option types
|
||||
- the "OptionDescription" is the schema, the option's structure
|
||||
- the "Config" which is the whole configuration entry point
|
||||
|
||||
![The Config](config.png "The Config")
|
||||
|
||||
## The handling of options
|
||||
|
||||
The handling of options is split into two parts: the description of
|
||||
which options are available, what their possible values and defaults are
|
||||
and how they are organized into a tree. A specific choice of options is
|
||||
bundled into a configuration object which has a reference to its option
|
||||
description (and therefore makes sure that the configuration values
|
||||
adhere to the option description).
|
||||
|
||||
- [Instanciate an option](option.md)
|
||||
- [Default Options](options.md)
|
||||
- [The symbolic link option: SymLinkOption](symlinkoption.md)
|
||||
- [Create it's own option](own_option.md)
|
||||
|
||||
## Option description are nested Options
|
||||
|
||||
The Option (in this case the "BoolOption"),
|
||||
are organized into a tree into nested "OptionDescription" objects.
|
||||
|
||||
Every option has a name, as does every option group.
|
||||
|
||||
- [Generic container: OptionDescription](optiondescription.md)
|
||||
- [Dynamic option description: DynOptionDescription](dynoptiondescription.md)
|
||||
- [Leadership OptionDescription: Leadership](leadership.md)
|
||||
|
||||
## Config
|
||||
|
||||
Getting started with the tiramisu library (it loads and prints properly).
|
||||
|
||||
Let's perform a *Getting started* code review :
|
||||
|
||||
```python
|
||||
from asyncio import run
|
||||
from tiramisu import Config
|
||||
from tiramisu import OptionDescription, BoolOption
|
||||
|
||||
# let's create a group of options named "optgroup"
|
||||
descr = OptionDescription("optgroup", "", [
|
||||
# ... with only one option inside
|
||||
BoolOption("bool", "", default=False)
|
||||
])
|
||||
|
||||
async def main():
|
||||
# Then, we make a Config with the OptionDescription` we
|
||||
cfg = await Config(descr)
|
||||
# the global help about the config
|
||||
cfg.help()
|
||||
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
Root config object that enables us to handle the configuration options
|
||||
|
||||
Settings:
|
||||
forcepermissive Access to option without verifying permissive properties
|
||||
unrestraint Access to option without property restriction
|
||||
|
||||
Commands:
|
||||
cache Manage config cache
|
||||
config Actions to Config
|
||||
information Manage config informations
|
||||
option Select an option
|
||||
owner Global owner
|
||||
permissive Manage config permissives
|
||||
property Manage config properties
|
||||
session Manage Config session
|
||||
value Manage config value
|
||||
```
|
||||
|
||||
Then let's print our "Option details.
|
||||
|
||||
```python
|
||||
cfg.option.help()
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
Select an option
|
||||
|
||||
Call: Select an option by path
|
||||
|
||||
Commands:
|
||||
dict Convert config and option to tiramisu format
|
||||
find Find an or a list of options
|
||||
list List options (by default list only option)
|
||||
updates Updates value with tiramisu format
|
||||
```
|
||||
|
||||
## Go futher with "Option" and "Config"
|
||||
|
||||
- [property](property.md)
|
||||
- [validator](validator.md)
|
|
@ -1,57 +0,0 @@
|
|||
# Dynamic option description: DynOptionDescription
|
||||
|
||||
## Dynamic option description's description
|
||||
|
||||
Dynamic option description is an OptionDescription which multiplies according to the return of a function.
|
||||
|
||||
First of all, an option description is a name, a description, children and suffixes.
|
||||
|
||||
### name
|
||||
|
||||
The "name" is important to retrieve this dynamic option description.
|
||||
|
||||
### description
|
||||
|
||||
The "description" allows the user to understand where this dynamic option description will contains.
|
||||
|
||||
### children
|
||||
|
||||
List of children option.
|
||||
|
||||
> **_NOTE:_** the option has to be multi option or leadership but not option description.
|
||||
|
||||
### suffixes
|
||||
|
||||
Suffixes is a [calculation](calculation.md) that return the list of suffixes used to create dynamic option description.
|
||||
|
||||
Let's try:
|
||||
|
||||
```python
|
||||
from tiramisu import StrOption, DynOptionDescription, Calculation
|
||||
def return_suffixes():
|
||||
return ['1', '2']
|
||||
|
||||
child1 = StrOption('first', 'First basic option ')
|
||||
child2 = StrOption('second', 'Second basic option ')
|
||||
DynOptionDescription('basic ',
|
||||
'Basic options ',
|
||||
[child1, child2],
|
||||
Calculation(return_suffixes))
|
||||
```
|
||||
|
||||
This example will construct:
|
||||
|
||||
- Basic options 1:
|
||||
|
||||
- First basic option 1
|
||||
- Second basic option 1
|
||||
|
||||
- Basic options 2:
|
||||
|
||||
- First basic option 2
|
||||
- Second basic option 2
|
||||
|
||||
## Dynamic option description's properties
|
||||
|
||||
See [property](property.md).
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
# Leadership OptionDescription: Leadership
|
||||
|
||||
A leadership is a special "OptionDescription" that wait a leader and one or multiple followers.
|
||||
|
||||
Leader and follower are multi option. The difference is that the length is defined by the length of the option leader.
|
||||
|
||||
If the length of leader is 3, all followers have also length 3.
|
||||
|
||||
An other different is that the follower is isolate. That means that you can only change on value on a specified index in the list of it's values.
|
||||
If a value is mark as modified in a specified index, that not affect the other values in other index.
|
||||
|
||||
## The leadership's description
|
||||
|
||||
A leadership is an "OptionDescription"
|
||||
|
||||
First of all, an option leadership is a name, a description and children.
|
||||
|
||||
### name
|
||||
|
||||
The "name" is important to retrieve this dynamic option description.
|
||||
|
||||
### description
|
||||
|
||||
The "description" allows the user to understand where this dynamic option description will contains.
|
||||
|
||||
### children
|
||||
|
||||
List of children option.
|
||||
|
||||
> **_NOTE:_** the option has to be multi or submulti option and not other option description.
|
||||
|
||||
Let's try:
|
||||
|
||||
```python
|
||||
from tiramisu import StrOption, Leadership
|
||||
users = StrOption('users', 'User', multi=True)
|
||||
passwords = StrOption('passwords', 'Password', multi=True)
|
||||
Leadership('users',
|
||||
'User allow to connect',
|
||||
[users, passwords])
|
||||
```
|
||||
|
||||
## The leadership's properties
|
||||
|
||||
See [property](property.md).
|
134
doc/option.md
|
@ -1,134 +0,0 @@
|
|||
# Instanciate an option
|
||||
|
||||
## Option's description
|
||||
|
||||
First of all, an option is a name and a description.
|
||||
|
||||
### name
|
||||
|
||||
The "name" is important to retrieve this option.
|
||||
|
||||
### description
|
||||
|
||||
The "description" allows the user to understand where this option will be used for.
|
||||
|
||||
Let's try:
|
||||
|
||||
```python
|
||||
from tiramisu import StrOption
|
||||
StrOption('welcome',
|
||||
'Welcome message to the user login')
|
||||
```
|
||||
|
||||
## Option's default value
|
||||
|
||||
For each option, we can defined a default value. This value will be the value of this option until user customize it.
|
||||
|
||||
This default value is store directly in the option. So we can, at any moment we can go back to the default value.
|
||||
|
||||
```python
|
||||
StrOption('welcome',
|
||||
'Welcome message to the user login',
|
||||
'Hey guys, welcome here!')
|
||||
```
|
||||
|
||||
The default value can be a calculation.
|
||||
|
||||
```python
|
||||
from tiramisu import Calculation
|
||||
def get_value():
|
||||
return 'Hey guys, welcome here'
|
||||
|
||||
StrOption('welcome',
|
||||
'Welcome message to the user login',
|
||||
Calculation(get_value))
|
||||
```
|
||||
|
||||
## Option with multiple value
|
||||
|
||||
### multi
|
||||
|
||||
There are cases where it can be interesting to have a list of values rather than just one.
|
||||
|
||||
The "multi" attribut is here for that.
|
||||
|
||||
In this case, the default value has to be a list:
|
||||
|
||||
```python
|
||||
StrOption('shopping_list',
|
||||
'The shopping list',
|
||||
['1 kilogram of carrots', 'leeks', '1 kilogram of potatos'],
|
||||
multi=True)
|
||||
```
|
||||
|
||||
The option could be a list of list, which is could submulti:
|
||||
|
||||
```python
|
||||
from tiramisu import submulti
|
||||
StrOption('shopping_list',
|
||||
'The shopping list',
|
||||
[['1 kilogram of carrots', 'leeks', '1 kilogram of potatos'], ['milk', 'eggs']],
|
||||
multi=submulti)
|
||||
```
|
||||
|
||||
The default value can be a calculation. For a multi, the function have to return a list or have to be in a list:
|
||||
|
||||
```python
|
||||
def get_values():
|
||||
return ['1 kilogram of carrots', 'leeks', '1 kilogram of potatos']
|
||||
|
||||
StrOption('shopping_list',
|
||||
'The shopping list',
|
||||
Calculation(get_values),
|
||||
multi=True)
|
||||
```
|
||||
|
||||
```python
|
||||
def get_a_value():
|
||||
return 'leeks'
|
||||
|
||||
StrOption('shopping_list',
|
||||
'The shopping list',
|
||||
['1 kilogram of carrots', Calculation(get_a_value), '1 kilogram of potatos'],
|
||||
multi=True)
|
||||
```
|
||||
|
||||
### default_multi
|
||||
|
||||
A second default value is available for multi option, "default_multi". This value is used when we add new value without specified a value.
|
||||
This "default_multi" must not be a list in multi purpose. For submulti, it has to be a list:
|
||||
|
||||
```python
|
||||
StrOption('shopping_list',
|
||||
'The shopping list',
|
||||
['1 kilogram of carrots', 'leeks', '1 kilogram of potatos'],
|
||||
default_multi='some vegetables',
|
||||
multi=True)
|
||||
StrOption('shopping_list',
|
||||
'The shopping list',
|
||||
[['1 kilogram of carrots', 'leeks', '1 kilogram of potatos'], ['milk', 'eggs']],
|
||||
default_multi=['some', 'vegetables'],
|
||||
multi=submulti)
|
||||
```
|
||||
|
||||
The default_multi value can be a calculation:
|
||||
|
||||
```python
|
||||
def get_a_value():
|
||||
return 'some vegetables'
|
||||
|
||||
StrOption('shopping_list',
|
||||
'The shopping list',
|
||||
['1 kilogram of carrots', 'leeks', '1 kilogram of potatos'],
|
||||
default_multi=Calculation(get_a_value),
|
||||
multi=True)
|
||||
```
|
||||
|
||||
## Other option's parameters
|
||||
|
||||
There are two other parameters.
|
||||
|
||||
We will see them later:
|
||||
|
||||
- [Property](property.md)
|
||||
- [Validator](validator.md)
|
|
@ -1,35 +0,0 @@
|
|||
# Generic container: OptionDescription
|
||||
|
||||
## Option description's description
|
||||
|
||||
First of all, an option description is a name, a description and children.
|
||||
|
||||
### name
|
||||
|
||||
The "name" is important to retrieve this option description.
|
||||
|
||||
### description
|
||||
|
||||
The "description" allows the user to understand where this option description will contains.
|
||||
|
||||
### children
|
||||
|
||||
List of children option.
|
||||
|
||||
> **_NOTE:_** the option can be an option or an other option description
|
||||
|
||||
Let's try:
|
||||
|
||||
```python
|
||||
from tiramisu import StrOption, OptionDescription
|
||||
child1 = StrOption('first', 'First basic option')
|
||||
child2 = StrOption('second', 'Second basic option')
|
||||
OptionDescription('basic',
|
||||
'Basic options',
|
||||
[child1, child2])
|
||||
```
|
||||
|
||||
## Option description's properties
|
||||
|
||||
See [property](property.md).
|
||||
|
501
doc/options.md
|
@ -1,501 +0,0 @@
|
|||
# Default Options
|
||||
|
||||
Basic options
|
||||
|
||||
## Textual option: StrOption
|
||||
|
||||
Option that accept any textual data in Tiramisu:
|
||||
|
||||
```python
|
||||
from tiramisu import StrOption
|
||||
StrOption('str', 'str', 'value')
|
||||
StrOption('str', 'str', '1')
|
||||
```
|
||||
|
||||
Other type generate an error:
|
||||
|
||||
```python
|
||||
try:
|
||||
StrOption('str', 'str', 1)
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
"1" is an invalid string for "str"
|
||||
```
|
||||
|
||||
|
||||
## Integers option: IntOption
|
||||
|
||||
Option that accept any integers number in Tiramisu:
|
||||
|
||||
```python
|
||||
from tiramisu import IntOption
|
||||
IntOption('int', 'int', 1)
|
||||
```
|
||||
|
||||
### min_number and max_number
|
||||
|
||||
This option can also verify minimal and maximal number:
|
||||
|
||||
```python
|
||||
IntOption('int', 'int', 10, min_number=10, max_number=15)
|
||||
```
|
||||
|
||||
```python
|
||||
try:
|
||||
IntOption('int', 'int', 16, max_number=15)
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
"16" is an invalid integer for "int", value must be less than "15"
|
||||
```
|
||||
|
||||
> **_NOTE:_** If "warnings_only" parameter it set to True, it will only emit a warning.
|
||||
|
||||
## Floating point number option: FloatOption
|
||||
|
||||
Option that accept any floating point number in Tiramisu:
|
||||
|
||||
```python
|
||||
from tiramisu import FloatOption
|
||||
FloatOption('float', 'float', 10.1)
|
||||
```
|
||||
|
||||
## Boolean option: BoolOption
|
||||
|
||||
Boolean values are the two constant objects False and True:
|
||||
|
||||
```python
|
||||
from tiramisu import BoolOption
|
||||
BoolOption('bool', 'bool', True)
|
||||
BoolOption('bool', 'bool', False)
|
||||
```
|
||||
|
||||
## Choice option: ChoiceOption
|
||||
|
||||
Option that only accepts a list of possible choices.
|
||||
|
||||
For example, we just want allowed 1 or 'see later':
|
||||
|
||||
```python
|
||||
from tiramisu import ChoiceOption
|
||||
ChoiceOption('choice', 'choice', (1, 'see later'), 1)
|
||||
ChoiceOption('choice', 'choice', (1, 'see later'), 'see later')
|
||||
```
|
||||
|
||||
Any other value isn't allowed:
|
||||
|
||||
```python
|
||||
try:
|
||||
ChoiceOption('choice', 'choice', (1, 'see later'), "i don't know")
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
"i don't know" is an invalid choice for "choice", only "1" and "see later" are allowed
|
||||
```
|
||||
|
||||
# Network options
|
||||
|
||||
## IPv4 address option: IPOption
|
||||
|
||||
An Internet Protocol address (IP address) is a numerical label assigned to each device connected to a computer network that uses the Internet Protocol for communication.
|
||||
|
||||
This option only support version 4 of the Internet Protocol.
|
||||
|
||||
```python
|
||||
from tiramisu import IPOption
|
||||
IPOption('ip', 'ip', '192.168.0.24')
|
||||
```
|
||||
|
||||
### private_only
|
||||
|
||||
By default IP could be a private or a public address. It's possible to restrict to only private IPv4 address with "private_only" attributs:
|
||||
|
||||
```python
|
||||
try:
|
||||
IPOption('ip', 'ip', '1.1.1.1', private_only=True)
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
"1.1.1.1" is an invalid IP for "ip", must be private IP
|
||||
```
|
||||
|
||||
> **_NOTE:_** If "warnings_only" parameter it set to True, it will only emit a warning.
|
||||
|
||||
### allow_reserved
|
||||
|
||||
By default, IETF reserved are not allowed. The "allow_reserved" can be used to allow this address:
|
||||
|
||||
```python
|
||||
try:
|
||||
IPOption('ip', 'ip', '255.255.255.255')
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
"255.255.255.255" is an invalid IP for "ip", mustn't be reserved IP
|
||||
```
|
||||
|
||||
But this doesn't raises:
|
||||
|
||||
```python
|
||||
IPOption('ip', 'ip', '255.255.255.255', allow_reserved=True)
|
||||
```
|
||||
|
||||
> **_NOTE:_** If "warnings_only" parameter it set to True, it will only emit a warning.
|
||||
|
||||
### cidr
|
||||
|
||||
Classless Inter-Domain Routing (CIDR) is a method for allocating IP addresses and IP routing.
|
||||
|
||||
CIDR notation, in which an address or routing prefix is written with a suffix indicating the number of bits of the prefix, such as 192.168.0.1/24.
|
||||
|
||||
```python
|
||||
IPOption('ip', 'ip', '192.168.0.1/24', cidr=True)
|
||||
try:
|
||||
IPOption('ip', 'ip', '192.168.0.0/24', cidr=True)
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
"192.168.0.0/24" is an invalid IP for "ip", it's in fact a network address
|
||||
```
|
||||
|
||||
## Port option: PortOption
|
||||
|
||||
A port is a network communication endpoint.
|
||||
|
||||
A port is a string object:
|
||||
|
||||
```python
|
||||
from tiramisu import PortOption
|
||||
PortOption('port', 'port', '80')
|
||||
```
|
||||
|
||||
### allow_range
|
||||
|
||||
A range is a list of port where we specified first port and last port number. The separator is ":":
|
||||
|
||||
```python
|
||||
PortOption('port', 'port', '2000', allow_range=True)
|
||||
PortOption('port', 'port', '2000:3000', allow_range=True)
|
||||
```
|
||||
|
||||
### allow_zero
|
||||
|
||||
By default, port 0 is not allowed, if you want allow it, use the parameter "allow_zero":
|
||||
|
||||
```python
|
||||
try:
|
||||
PortOption('port', 'port', '0')
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```python
|
||||
"0" is an invalid port for "port", must be between 1 and 49151
|
||||
```
|
||||
|
||||
But this doesn't raises:
|
||||
|
||||
```python
|
||||
PortOption('port', 'port', '0', allow_zero=True)
|
||||
```
|
||||
|
||||
> **_NOTE:_** This option affect the minimal and maximal port number, if "warnings_only" parameter it set to True, it will only emit a warning.
|
||||
|
||||
### allow_wellknown
|
||||
|
||||
The well-known ports (also known as system ports) are those from 1 through 1023. This parameter is set to True by default:
|
||||
|
||||
```python
|
||||
PortOption('port', 'port', '80')
|
||||
```
|
||||
|
||||
```python
|
||||
try:
|
||||
PortOption('port', 'port', '80', allow_wellknown=False)
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
"80" is an invalid port for "port", must be between 1024 and 49151
|
||||
```
|
||||
|
||||
> **_NOTE:_** This option affect the minimal and maximal port number, if "warnings_only" parameter it set to True, it will only emit a warning.
|
||||
|
||||
### allow_registred
|
||||
|
||||
The registered ports are those from 1024 through 49151. This parameter is set to True by default:
|
||||
|
||||
```python
|
||||
PortOption('port', 'port', '1300')
|
||||
```
|
||||
|
||||
```python
|
||||
try:
|
||||
PortOption('port', 'port', '1300', allow_registred=False)
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
"1300" is an invalid port for "port", must be between 1 and 1023
|
||||
```
|
||||
|
||||
> **_NOTE:_** This option affect the minimal and maximal port number, if "warnings_only" parameter it set to True, it will only emit a warning.
|
||||
|
||||
### allow_protocol
|
||||
|
||||
```python
|
||||
PortOption('port', 'port', '1300', allow_protocol="True")
|
||||
PortOption('port', 'port', 'tcp:1300', allow_protocol="True")
|
||||
PortOption('port', 'port', 'udp:1300', allow_protocol="True")
|
||||
```
|
||||
|
||||
### allow_private
|
||||
|
||||
The dynamic or private ports are those from 49152 through 65535. One common use for this range is for ephemeral ports. This parameter is set to False by default:
|
||||
|
||||
```python
|
||||
try:
|
||||
PortOption('port', 'port', '64000')
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
"64000" is an invalid port for "port", must be between 1 and 49151
|
||||
```
|
||||
|
||||
```python
|
||||
PortOption('port', 'port', '64000', allow_private=True)
|
||||
```
|
||||
|
||||
> **_NOTE:_** This option affect the minimal and maximal port number, if "warnings_only" parameter it set to True, it will only emit a warning.
|
||||
|
||||
## Subnetwork option: NetworkOption
|
||||
|
||||
IP networks may be divided into subnetworks:
|
||||
|
||||
```python
|
||||
from tiramisu import NetworkOption
|
||||
NetworkOption('net', 'net', '192.168.0.0')
|
||||
```
|
||||
|
||||
### cidr
|
||||
|
||||
Classless Inter-Domain Routing (CIDR) is a method for allocating IP addresses and IP routing.
|
||||
|
||||
CIDR notation, in which an address or routing prefix is written with a suffix indicating the number of bits of the prefix, such as 192.168.0.0/24:
|
||||
|
||||
```python
|
||||
NetworkOption('net', 'net', '192.168.0.0/24', cidr=True)
|
||||
```
|
||||
|
||||
## Subnetwork mask option: NetmaskOption
|
||||
|
||||
For IPv4, a network may also be characterized by its subnet mask or netmask. This option allow you to enter a netmask:
|
||||
|
||||
```python
|
||||
from tiramisu import NetmaskOption
|
||||
NetmaskOption('mask', 'mask', '255.255.255.0')
|
||||
```
|
||||
|
||||
## Broadcast option: BroadcastOption
|
||||
|
||||
The last address within a network broadcast transmission to all hosts on the link. This option allow you to enter a broadcast:
|
||||
|
||||
```python
|
||||
from tiramisu import BroadcastOption
|
||||
BroadcastOption('bcast', 'bcast', '192.168.0.254')
|
||||
```
|
||||
|
||||
# Internet options
|
||||
|
||||
## Domain name option: DomainnameOption
|
||||
|
||||
Domain names are used in various networking contexts and for application-specific naming and addressing purposes.
|
||||
The DomainnameOption allow different type of domain name:
|
||||
|
||||
```python
|
||||
from tiramisu import DomainnameOption
|
||||
DomainnameOption('domain', 'domain', 'foo.example.net')
|
||||
DomainnameOption('domain', 'domain', 'foo', type='hostname')
|
||||
```
|
||||
|
||||
### type
|
||||
|
||||
There is three type for a domain name:
|
||||
|
||||
- "domainname" (default): lowercase, number, "-" and "." characters are allowed, this must have at least one "."
|
||||
- "hostname": lowercase, number and "-" characters are allowed, the maximum length is 63 characters
|
||||
- "netbios": lowercase, number and "-" characters are allowed, the maximum length is 15 characters
|
||||
|
||||
> **_NOTE:_** If "warnings_only" parameter it set to True, it will raise if length is incorrect by only emit a warning character is not correct.
|
||||
|
||||
### allow_ip
|
||||
|
||||
If the option can contain a domain name or an IP, you can set the "allow_ip" to True, (False by default):
|
||||
|
||||
```python
|
||||
DomainnameOption('domain', 'domain', 'foo.example.net', allow_ip=True)
|
||||
DomainnameOption('domain', 'domain', '192.168.0.1', allow_ip=True)
|
||||
```
|
||||
|
||||
In this case, IP is validate has IPOption would do.
|
||||
|
||||
### allow_cidr_network
|
||||
|
||||
If the option can contain a CIDR network, you can set "allow_cidr_network" to True, (False by default):
|
||||
|
||||
```python
|
||||
DomainnameOption('domain', 'domain', 'foo.example.net', allow_cidr_network=True)
|
||||
DomainnameOption('domain', 'domain', '192.168.0.0/24', allow_cidr_network=True)
|
||||
```
|
||||
|
||||
### allow_without_dot
|
||||
|
||||
A domain name with domainname's type must have a dot. If "allow_without_dot" is True (False by default), we can set a domainname or an hostname:
|
||||
|
||||
```python
|
||||
DomainnameOption('domain', 'domain', 'foo.example.net', allow_without_dot=True)
|
||||
DomainnameOption('domain', 'domain', 'foo', allow_without_dot=True)
|
||||
```
|
||||
|
||||
### allow_startswith_dot
|
||||
|
||||
A domain name with domainname's type mustn't start by a dot. .example.net is not a valid domain. In some case it could be interesting to allow domain name starts by a dot (for ACL in Squid, no proxy option in Firefox, ...):
|
||||
|
||||
```python
|
||||
DomainnameOption('domain', 'domain', 'example.net', allow_startswith_dot=True)
|
||||
DomainnameOption('domain', 'domain', '.example.net', allow_startswith_dot=True)
|
||||
```
|
||||
|
||||
## MAC address option: MACOption
|
||||
|
||||
Validate MAC address:
|
||||
|
||||
```python
|
||||
from tiramisu import MACOption
|
||||
MACOption('mac', 'mac', '01:10:20:20:10:30')
|
||||
```
|
||||
|
||||
## Uniform Resource Locator option: UrlOption
|
||||
|
||||
An Uniform Resource Locator is, in fact, a string starting with http:// or https://, a DomainnameOption, optionaly ':' and a PortOption, and finally filename:
|
||||
|
||||
```python
|
||||
from tiramisu import URLOption
|
||||
URLOption('url', 'url', 'http://foo.example.fr/index.php')
|
||||
URLOption('url', 'url', 'https://foo.example.fr:4200/index.php?login=foo&pass=bar')
|
||||
```
|
||||
|
||||
For parameters, see PortOption and DomainnameOption.
|
||||
|
||||
## Email address option: EmailOption
|
||||
|
||||
Electronic mail (email or e-mail) is a method of exchanging messages ("mail") between people using electronic devices. Here an example:
|
||||
|
||||
```python
|
||||
from tiramisu import EmailOption
|
||||
EmailOption('mail', 'mail', 'foo@example.net')
|
||||
```
|
||||
|
||||
# Unix options
|
||||
|
||||
## Unix username: UsernameOption
|
||||
|
||||
An unix username option is a 32 characters maximum length with lowercase ASCII characters, number, '_' or '-'. The username have to start with lowercase ASCII characters or "_":
|
||||
|
||||
```python
|
||||
from tiramisu import UsernameOption
|
||||
UsernameOption('user', 'user', 'my_user')
|
||||
```
|
||||
|
||||
## Unix groupname: GroupnameOption
|
||||
|
||||
Same condition for unix group name:
|
||||
|
||||
```python
|
||||
from tiramisu import GroupnameOption
|
||||
GroupnameOption('group', 'group', 'my_group')
|
||||
```
|
||||
|
||||
## Password option: PasswordOption
|
||||
|
||||
Simple string with no other restriction:
|
||||
|
||||
```python
|
||||
from tiramisu import PasswordOption
|
||||
PasswordOption('pass', 'pass', 'oP$¨1jiJie')
|
||||
```
|
||||
|
||||
## Unix filename option: FilenameOption
|
||||
|
||||
For this option, only lowercase and uppercas ASCII character, "-", ".", "_", "~", and "/" are allowed:
|
||||
|
||||
```python
|
||||
from tiramisu import FilenameOption
|
||||
FilenameOption('file', 'file', '/etc/tiramisu/tiramisu.conf')
|
||||
```
|
||||
|
||||
## Unix file permissions: PermissionsOption
|
||||
|
||||
Valid the representing Unix permissions is an octal (base-8) notation.
|
||||
|
||||
```python
|
||||
from tiramisu import PermissionsOption
|
||||
PermissionsOption('perms', 'perms', 755)
|
||||
PermissionsOption('perms', 'perms', 1755)
|
||||
```
|
||||
|
||||
This option doesn't allow (or display a warning with warnings_only):
|
||||
|
||||
- 777 (two weak value)
|
||||
- others have more right than group
|
||||
- group has more right than user
|
||||
|
||||
# Date option
|
||||
|
||||
## Date option: DateOption
|
||||
|
||||
Date option waits for a date with format YYYY-MM-DD:
|
||||
|
||||
```python
|
||||
from tiramisu import DateOption
|
||||
DateOption('date', 'date', '2019-10-30')
|
||||
```
|
||||
|
||||
|
|
@ -1,183 +0,0 @@
|
|||
# Create it's own option
|
||||
|
||||
## Generic regexp option: "RegexpOption"
|
||||
|
||||
Use "RegexpOption" to create custom option is very simple.
|
||||
|
||||
You just have to create an object that inherits from "RegexpOption" and that has the following class attributes:
|
||||
|
||||
- \_\_slots\_\_: with new data members (the values should always be "tuple()")
|
||||
- \_type = with a name
|
||||
- \_display_name: with the display name (for example in error message)
|
||||
- \_regexp: with a compiled regexp
|
||||
|
||||
Here an example to an option that only accept string with on lowercase ASCII vowel characters:
|
||||
|
||||
```python
|
||||
import re
|
||||
from tiramisu import RegexpOption
|
||||
class VowelOption(RegexpOption):
|
||||
__slots__ = tuple()
|
||||
_type = 'vowel'
|
||||
_display_name = "string with vowel"
|
||||
_regexp = re.compile(r"^[aeiouy]*$")
|
||||
```
|
||||
|
||||
Let's try our object:
|
||||
|
||||
```python
|
||||
VowelOption('vowel', 'Vowel', 'aae')
|
||||
```
|
||||
|
||||
```python
|
||||
try:
|
||||
VowelOption('vowel', 'Vowel', 'oooups')
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```python
|
||||
"oooups" is an invalid string with vowel for "Vowel"
|
||||
```
|
||||
|
||||
## Create a custom option
|
||||
|
||||
An option always inherits from "Option" object. This object has the following class attributes:
|
||||
|
||||
- \_\_slots\_\_: a tuple with new data members (by default you should set "tuple()")
|
||||
- \_type = with a name
|
||||
- \_display_name: with the display name (for example in error message)
|
||||
|
||||
Here an example to an lipogram option:
|
||||
|
||||
```python
|
||||
from tiramisu import Option
|
||||
from tiramisu.error import ValueWarning
|
||||
import warnings
|
||||
|
||||
class LipogramOption(Option):
|
||||
__slots__ = tuple()
|
||||
_type = 'lipogram'
|
||||
_display_name = 'lipogram'
|
||||
#
|
||||
# First of all we want to add a custom parameter to ask the minimum length (min_len) of the value:
|
||||
def __init__(self,
|
||||
*args,
|
||||
min_len=100,
|
||||
**kwargs):
|
||||
# store extra parameters
|
||||
extra = {'_min_len': min_len}
|
||||
super().__init__(*args,
|
||||
extra=extra,
|
||||
**kwargs)
|
||||
#
|
||||
# We have a first validation method.
|
||||
# In this method, we verify that the value is a string and that there is no "e" on it:
|
||||
# Even if user set warnings_only attribute, this method will raise.
|
||||
def validate(self,
|
||||
value):
|
||||
# first, valid that the value is a string
|
||||
if not isinstance(value, str):
|
||||
raise ValueError('invalid string')
|
||||
# and verify that there is any 'e' in the sentense
|
||||
if 'e' in value:
|
||||
raise ValueError('Perec wrote a book without any "e", you could not do it in a simple sentence?')
|
||||
#
|
||||
# Finally we add a method to valid the value length.
|
||||
# If "warnings_only" is set to True, a warning will be emit:
|
||||
def second_level_validation(self,
|
||||
value,
|
||||
warnings_only):
|
||||
# retrive parameter in extra
|
||||
min_len = self.impl_get_extra('_min_len')
|
||||
# verify the sentense length
|
||||
if len(value) < min_len:
|
||||
# raise message, in this case, warning and error message are different
|
||||
if warnings_only:
|
||||
msg = f'it would be better to have at least {min_len} characters in the sentence'
|
||||
else:
|
||||
msg = f'you must have at least {min_len} characters in the sentence'
|
||||
raise ValueError(msg)
|
||||
```
|
||||
|
||||
Let's test it:
|
||||
|
||||
1. the character "e" is in the value:
|
||||
|
||||
```python
|
||||
try:
|
||||
LipogramOption('lipo',
|
||||
'Lipogram',
|
||||
'I just want to add a quality string that has no bad characters')
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
"I just want to add a quality string that has no bad characters" is an invalid lipogram for "Lipogram", Perec wrote a book without any "e", you could not do it in a simple sentence?
|
||||
```
|
||||
|
||||
2. the character "e" is in the value and warnings_only is set to True:
|
||||
|
||||
```python
|
||||
try:
|
||||
LipogramOption('lipo',
|
||||
'Lipogram',
|
||||
'I just want to add a quality string that has no bad characters',
|
||||
warnings_only=True)
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
```
|
||||
|
||||
```
|
||||
"I just want to add a quality string that has no bad characters" is an invalid lipogram for "Lipogram", Perec wrote a book without any "e", you could not do it in a simple sentence?
|
||||
```
|
||||
|
||||
3. the value is too short
|
||||
|
||||
```python
|
||||
try:
|
||||
LipogramOption('lipo',
|
||||
'Lipogram',
|
||||
'I just want to add a quality string that has no bad symbols')
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
"I just want to add a quality string that has no bad symbols" is an invalid lipogram for "Lipogram", you must have at least 100 characters in the sentence
|
||||
```
|
||||
|
||||
4. the value is too short and warnings_only is set to True:
|
||||
|
||||
```python
|
||||
warnings.simplefilter('always', ValueWarning)
|
||||
with warnings.catch_warnings(record=True) as warn:
|
||||
LipogramOption('lipo',
|
||||
'Lipogram',
|
||||
'I just want to add a quality string that has no bad symbols',
|
||||
warnings_only=True)
|
||||
if warn:
|
||||
print(warn[0].message)
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
attention, "I just want to add a quality string that has no bad symbols" could be an invalid lipogram for "Lipogram", it would be better to have at least 100 characters in the sentence
|
||||
```
|
||||
|
||||
5. set minimum length to 50 characters, the value is valid:
|
||||
|
||||
```python
|
||||
LipogramOption('lipo',
|
||||
'Lipogram',
|
||||
'I just want to add a quality string that has no bad symbols',
|
||||
min_len=50)
|
||||
```
|
109
doc/property.md
|
@ -1,109 +0,0 @@
|
|||
# Property
|
||||
|
||||
## What are properties ?
|
||||
|
||||
The properties are a central element of Tiramisu.
|
||||
|
||||
Properties change the behavior of an option or make it unavailable.
|
||||
|
||||
## Read only and read write
|
||||
|
||||
Config can be in two defaut mode:
|
||||
|
||||
### read_only
|
||||
|
||||
You can get all variables in a "Config" that not disabled.
|
||||
|
||||
Off course, as the Config is read only, you cannot set any value to any option.
|
||||
Only value with :doc`calculation` can change value.
|
||||
You cannot access to mandatory variable without values. Verify that all values is set before change mode.
|
||||
|
||||
### read_write
|
||||
|
||||
You can get all options not disabled and not hidden. You can also set all variables not frozen.
|
||||
|
||||
## Common properties
|
||||
|
||||
### hidden
|
||||
|
||||
Option with this property can only get value in read only mode.
|
||||
This property is used for option that user cannot modifify it's value (for example if it's value is calculated).
|
||||
|
||||
### disabled
|
||||
|
||||
We never can access to option with this property.
|
||||
|
||||
### frozen
|
||||
|
||||
Options with this property cannot be modified.
|
||||
|
||||
## Special option properties
|
||||
|
||||
### mandatory
|
||||
|
||||
You should set value for option with this properties. In read only mode we cannot access to this option if no value is set.
|
||||
|
||||
### empty or notempty
|
||||
|
||||
Only used for multi option that are not a follower.
|
||||
|
||||
Mandatory for a multi means that you cannot add None as a value. But value [] is allowed. This is not permit with "empty" property.
|
||||
|
||||
A multi option has automaticly "empty" property. If you don't want allow empty option, just add "notempty" property when you create the option.
|
||||
|
||||
### unique or notunique
|
||||
|
||||
Only used for multi option that are not a follower.
|
||||
|
||||
Raise ValueError if a value is set twice or more in a multi Option.
|
||||
|
||||
A multi option has automaticly "unique" property. If you want allow duplication in option, just add "notunique" property when you create the option.
|
||||
|
||||
### permissive
|
||||
|
||||
Option with 'permissive' cannot raise PropertiesOptionError for properties set in permissive.
|
||||
|
||||
Config with 'permissive', whole option in this config cannot raise PropertiesOptionError for properties set in permissive.
|
||||
|
||||
## Special Config properties
|
||||
|
||||
### cache
|
||||
|
||||
Enable cache settings and values.
|
||||
|
||||
### expire
|
||||
|
||||
Enable settings and values in cache expire after "expiration_time" (by default 5 seconds).
|
||||
|
||||
### everything_frozen
|
||||
|
||||
Whole options in config are frozen (even if option have not frozen property).
|
||||
|
||||
|
||||
### force_default_on_freeze
|
||||
|
||||
Whole options frozen in config will lost his value and use default values.
|
||||
|
||||
### force_metaconfig_on_freeze
|
||||
|
||||
Whole options frozen in config will lost his value and get MetaConfig values.
|
||||
|
||||
### force_store_value
|
||||
|
||||
All options with this properties will be mark has modified.
|
||||
|
||||
### validator
|
||||
|
||||
Launch validator set by user in option (this property has no effect for option validation and second level validation).
|
||||
|
||||
### warnings
|
||||
|
||||
Display warnings during validation.
|
||||
|
||||
### demoting_error_warning
|
||||
|
||||
All value's errors are convert to warning (ValueErrorWarning).
|
||||
|
||||
## Own properties
|
||||
|
||||
There are no specific instructions for creating a property. Just add a string as property create a new property.
|
|
@ -1,13 +0,0 @@
|
|||
# The symbolic link option: SymLinkOption
|
||||
|
||||
A "SymLinkOption" is an option that actually points to another option.
|
||||
|
||||
Each time we will access to a properties of this options, we will have in return the value of other option.
|
||||
|
||||
Creation a "SymLinkOption" is easy:
|
||||
|
||||
```python
|
||||
from tiramisu import StrOption, SymLinkOption
|
||||
st = StrOption('str', 'str')
|
||||
sym = SymLinkOption('sym', st)
|
||||
```
|
494
doc/validator.md
|
@ -1,494 +0,0 @@
|
|||
# Validator
|
||||
|
||||
## What are validator ?
|
||||
|
||||
Validator is a functionnality that allow you to call a function to determine if an option is valid.
|
||||
|
||||
To define validator we have to use [calculation](calculation.md) object.
|
||||
The function have to raise a "ValueError" object if the value is not valid. It could emit a warning when raises a "ValueWarning".
|
||||
|
||||
## Validator with options
|
||||
|
||||
Here an example, where we want to ask a new password to an user. This password should not be weak. The password will be asked twice and must match.
|
||||
|
||||
First of all, import necessary object:
|
||||
|
||||
```python
|
||||
from asyncio import run
|
||||
import warnings
|
||||
from re import match
|
||||
from tiramisu import StrOption, IntOption, OptionDescription, Config, \
|
||||
Calculation, Params, ParamOption, ParamSelfOption, ParamValue
|
||||
from tiramisu.error import ValueWarning
|
||||
```
|
||||
|
||||
Create a first function to valid that the password is not weak:
|
||||
|
||||
```python
|
||||
def is_password_conform(password):
|
||||
# password must containe at least a number, a lowercase letter, an uppercase letter and a symbol
|
||||
if not match(r'(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*\W)', password):
|
||||
raise ValueError('please choose a stronger password, try a mix of letters, numbers and symbols')
|
||||
```
|
||||
|
||||
Secondly create a function to valid password length. The password must be longer than the value of "min_len" and should be longer than the value of "recommand_len".
|
||||
|
||||
In first case, function raise "ValueError", this value is incorrect.
|
||||
|
||||
In second case, function raise "ValueWarning", the value is valid but discouraged:
|
||||
|
||||
```python
|
||||
def password_correct_len(min_len, recommand_len, password):
|
||||
# password must have at least min_len characters
|
||||
if len(password) < min_len:
|
||||
raise ValueError(f'use {min_len} characters or more for your password')
|
||||
# password should have at least recommand_len characters
|
||||
if len(password) < recommand_len:
|
||||
raise ValueWarning(f'it would be better to use more than {recommand_len} characters for your password')
|
||||
```
|
||||
|
||||
Thirdly create a function that verify that the login name is not a part of password (password "foo2aZ$" if not valid for user "foo"):
|
||||
|
||||
|
||||
```python
|
||||
def user_not_in_password(login, password):
|
||||
if login in password:
|
||||
raise ValueError('the login must not be part of the password')
|
||||
```
|
||||
|
||||
Now we can creation an option to ask user login:
|
||||
|
||||
```python
|
||||
login = StrOption('login', 'Login', properties=('mandatory',))
|
||||
```
|
||||
|
||||
Create a calculation to launch "is_password_conform". This function will be use in a new option and must validate this new option. So we use the object "ParamSelfOption" has parameter to retrieve the value of current option:
|
||||
|
||||
```python
|
||||
calc1 = Calculation(is_password_conform,
|
||||
Params(ParamSelfOption()))
|
||||
```
|
||||
|
||||
Create a second calculation to launch "password_correct_len" function. We want set 8 as "min_len" value and 12 as "recommand_len" value:
|
||||
|
||||
```python
|
||||
calc2 = Calculation(password_correct_len,
|
||||
Params((ParamValue(8),
|
||||
ParamValue(12),
|
||||
ParamSelfOption())))
|
||||
```
|
||||
|
||||
Create a third calculation to launch "user_not_in_password" function. For this function, we use keyword argument. This function normaly raise "ValueError" but in this case we want demoting this error as a simple warning. So we add "warnings_only" parameter:
|
||||
|
||||
```python
|
||||
calc3 = Calculation(user_not_in_password,
|
||||
Params(kwargs={'login': ParamOption(login),
|
||||
'password': ParamSelfOption()}),
|
||||
warnings_only=True)
|
||||
```
|
||||
|
||||
So now we can create first password option that use those calculations:
|
||||
|
||||
```python
|
||||
password1 = StrOption('password1',
|
||||
'Password',
|
||||
properties=('mandatory',),
|
||||
validators=[calc1, calc2, calc3])
|
||||
```
|
||||
|
||||
A new function is created to conform that password1 and password2 match:
|
||||
|
||||
```python
|
||||
def password_match(password1, password2):
|
||||
if password1 != password2:
|
||||
raise ValueError("those passwords didn't match, try again")
|
||||
```
|
||||
|
||||
And now we can create second password option that use this function:
|
||||
|
||||
```python
|
||||
password2 = StrOption('password2',
|
||||
'Confirm',
|
||||
properties=('mandatory',),
|
||||
validators=[Calculation(password_match, Params((ParamOption(password1), ParamSelfOption())))])
|
||||
```
|
||||
|
||||
Finally we create optiondescription and config:
|
||||
|
||||
```python
|
||||
od = OptionDescription('password', 'Define your password', [password1, password2])
|
||||
root = OptionDescription('root', '', [login, od])
|
||||
async def main():
|
||||
config = await Config(root)
|
||||
await config.property.read_write()
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
Now we can test this "Config" with a tested password too weak:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
config = await Config(root)
|
||||
await config.property.read_write()
|
||||
await config.option('login').value.set('user')
|
||||
try:
|
||||
await config.option('password.password1').value.set('aAbBc')
|
||||
except ValueError as err:
|
||||
print(f'Error: {err}')
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
Error: "aAbBc" is an invalid string for "Password", please choose a stronger password, try a mix of letters, numbers and symbols
|
||||
```
|
||||
|
||||
The password is part of error message. In this case it's a bad idea. So we have to remove "prefix" to the error message:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
config = await Config(root)
|
||||
await config.property.read_write()
|
||||
await config.option('login').value.set('user')
|
||||
try:
|
||||
await config.option('password.password1').value.set('aAbBc')
|
||||
except ValueError as err:
|
||||
err.prefix = ''
|
||||
print(f'Error: {err}')
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
Now the error is:
|
||||
|
||||
```
|
||||
Error: please choose a stronger password, try a mix of letters, numbers and symbols
|
||||
```
|
||||
|
||||
Let's try with a password not weak but too short:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
config = await Config(root)
|
||||
await config.property.read_write()
|
||||
await config.option('login').value.set('user')
|
||||
try:
|
||||
await config.option('password.password1').value.set('aZ$1')
|
||||
except ValueError as err:
|
||||
err.prefix = ''
|
||||
print(f'Error: {err}')
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
The error is:
|
||||
|
||||
```
|
||||
Error: use 8 characters or more for your password
|
||||
```
|
||||
|
||||
Now try a password with 8 characters:
|
||||
|
||||
```python
|
||||
warnings.simplefilter('always', ValueWarning)
|
||||
async def main():
|
||||
config = await Config(root)
|
||||
await config.property.read_write()
|
||||
await config.option('login').value.set('user')
|
||||
with warnings.catch_warnings(record=True) as warn:
|
||||
await config.option('password.password1').value.set('aZ$1bN:2')
|
||||
if warn:
|
||||
warn[0].message.prefix = ''
|
||||
print(f'Warning: {warn[0].message}')
|
||||
password = await config.option('password.password1').value.get()
|
||||
print(f'The password is "{password}"')
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
Warning is display but password is store:
|
||||
|
||||
```
|
||||
Warning: it would be better to use more than 12 characters for your password
|
||||
The password is "aZ$1bN:2"
|
||||
```
|
||||
|
||||
Try a password with the login as part of it:
|
||||
|
||||
```python
|
||||
warnings.simplefilter('always', ValueWarning)
|
||||
async def main():
|
||||
config = await Config(root)
|
||||
await config.property.read_write()
|
||||
await config.option('login').value.set('user')
|
||||
with warnings.catch_warnings(record=True) as warn:
|
||||
await config.option('password.password1').value.set('aZ$1bN:2u@1Bjuser')
|
||||
if warn:
|
||||
warn[0].message.prefix = ''
|
||||
print(f'Warning: {warn[0].message}')
|
||||
password = await config.option('password.password1').value.get()
|
||||
print(f'The password is "{password}"')
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
Warning is display but password is store:
|
||||
|
||||
```
|
||||
Warning: the login must not be part of the password
|
||||
The password is "aZ$1bN:2u@1Bjuser"
|
||||
```
|
||||
|
||||
Now try with a valid password but that doesn't match:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
config = await Config(root)
|
||||
await config.property.read_write()
|
||||
await config.option('login').value.set('user')
|
||||
await config.option('password.password1').value.set('aZ$1bN:2u@1Bj')
|
||||
try:
|
||||
await config.option('password.password2').value.set('aZ$1aaaa')
|
||||
except ValueError as err:
|
||||
err.prefix = ''
|
||||
print(f'Error: {err}')
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
An error is displayed:
|
||||
|
||||
```
|
||||
Error: those passwords didn't match, try again
|
||||
```
|
||||
|
||||
Finally try a valid password:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
config = await Config(root)
|
||||
await config.property.read_write()
|
||||
await config.option('login').value.set('user')
|
||||
await config.option('login').value.set('user')
|
||||
await config.option('password.password1').value.set('aZ$1bN:2u@1Bj')
|
||||
await config.option('password.password2').value.set('aZ$1bN:2u@1Bj')
|
||||
await config.property.read_only()
|
||||
user_login = await config.option('login').value.get()
|
||||
password = await config.option('password.password2').value.get()
|
||||
print(f'The password for "{user_login}" is "{password}"')
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
As expected, we have:
|
||||
|
||||
```
|
||||
The password for "user" is "aZ$1bN:2u@1Bj"
|
||||
```
|
||||
|
||||
## Validator with a multi option
|
||||
|
||||
Assume we ask percentage value to an user. The sum of values mustn't be higher than 100% and shouldn't be lower than 100%.
|
||||
|
||||
Let's start by importing the objects:
|
||||
|
||||
```python
|
||||
from asyncio import run
|
||||
import warnings
|
||||
from tiramisu import IntOption, OptionDescription, Config, \
|
||||
Calculation, Params, ParamSelfOption
|
||||
from tiramisu.error import ValueWarning
|
||||
```
|
||||
|
||||
Continue by writing the validation function:
|
||||
|
||||
```python
|
||||
def valid_pourcent(option):
|
||||
total = sum(option)
|
||||
if total > 100:
|
||||
raise ValueError(f'the total {total}% is bigger than 100%')
|
||||
if total < 100:
|
||||
raise ValueWarning(f'the total {total}% is lower than 100%')
|
||||
```
|
||||
|
||||
And create a simple option in a single OptionDescription:
|
||||
|
||||
```python
|
||||
percent = IntOption('percent',
|
||||
'Percent',
|
||||
multi=True,
|
||||
validators=[Calculation(valid_pourcent, Params(ParamSelfOption()))])
|
||||
od = OptionDescription('root', 'root', [percent])
|
||||
```
|
||||
|
||||
Now try with bigger sum:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
config = await Config(od)
|
||||
try:
|
||||
await config.option('percent').value.set([20, 90])
|
||||
except ValueError as err:
|
||||
err.prefix = ''
|
||||
print(f'Error: {err}')
|
||||
percent_value = await config.option('percent').value.get()
|
||||
print(f'The value is "{percent_value}"')
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
The result is:
|
||||
|
||||
```
|
||||
Error: the total 110% is bigger than 100%
|
||||
The value is "[]"
|
||||
```
|
||||
|
||||
Let's try with lower sum:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
config = await Config(od)
|
||||
warnings.simplefilter('always', ValueWarning)
|
||||
with warnings.catch_warnings(record=True) as warn:
|
||||
await config.option('percent').value.set([20, 70])
|
||||
if warn:
|
||||
warn[0].message.prefix = ''
|
||||
print(f'Warning: {warn[0].message}')
|
||||
percent_value = await config.option('percent').value.get()
|
||||
print(f'The value is "{percent_value}"')
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
The result is:
|
||||
|
||||
```
|
||||
Warning: the total 90% is lower than 100%
|
||||
The value is "[20, 70]"
|
||||
```
|
||||
|
||||
Finally with correct value:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
config = await Config(od)
|
||||
await config.option('percent').value.set([20, 80])
|
||||
percent_value = await config.option('percent').value.get()
|
||||
print(f'The value is "{percent_value}"')
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
The result is:
|
||||
|
||||
```
|
||||
The value is "[20, 80]"
|
||||
```
|
||||
|
||||
## Validator with a follower option
|
||||
|
||||
Assume we want distribute something to differents users. The sum of values mustn't be higher than 100%.
|
||||
|
||||
First, import all needed objects:
|
||||
|
||||
```python
|
||||
from asyncio import run
|
||||
import warnings
|
||||
from tiramisu import StrOption, IntOption, Leadership, OptionDescription, Config, \
|
||||
Calculation, Params, ParamSelfOption, ParamIndex
|
||||
from tiramisu.error import ValueWarning
|
||||
```
|
||||
|
||||
Let's start to write a function with three arguments:
|
||||
|
||||
- the first argument will have all values set for the follower
|
||||
- the second argument will have only last value set for the follower
|
||||
- the third argument will have the index
|
||||
|
||||
```python
|
||||
def valid_pourcent(option, current_option, index):
|
||||
if None in option:
|
||||
return
|
||||
total = sum(option)
|
||||
if total > 100:
|
||||
raise ValueError(f'the value {current_option} (at index {index}) is too big, the total is {total}%')
|
||||
```
|
||||
|
||||
Continue by creating a calculation:
|
||||
|
||||
```python
|
||||
calculation = Calculation(valid_pourcent, Params((ParamSelfOption(whole=True),
|
||||
ParamSelfOption(),
|
||||
ParamIndex())))
|
||||
```
|
||||
|
||||
And instanciate differents option:
|
||||
|
||||
```python
|
||||
user = StrOption('user', 'User', multi=True)
|
||||
percent = IntOption('percent',
|
||||
'Distribution',
|
||||
multi=True,
|
||||
validators=[calculation])
|
||||
leader = Leadership('percent', 'Percent', [user, percent])
|
||||
od = OptionDescription('root', 'root', [leader])
|
||||
```
|
||||
|
||||
Add two values to the leader:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
config = await Config(od)
|
||||
await config.option('percent.user').value.set(['user1', 'user2'])
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
The user user1 will have 20%:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
config = await Config(od)
|
||||
await config.option('percent.user').value.set(['user1', 'user2'])
|
||||
await config.option('percent.percent', 0).value.set(20)
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
If we try to set 90% to user2:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
config = await Config(od)
|
||||
await config.option('percent.user').value.set(['user1', 'user2'])
|
||||
await config.option('percent.percent', 0).value.set(20)
|
||||
try:
|
||||
await config.option('percent.percent', 1).value.set(90)
|
||||
except ValueError as err:
|
||||
err.prefix = ''
|
||||
print(f'Error: {err}')
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
results:
|
||||
|
||||
```
|
||||
Error: the value 90 (at index 1) is too big, the total is 110%
|
||||
```
|
||||
|
||||
No problem with 80%:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
config = await Config(od)
|
||||
await config.option('percent.user').value.set(['user1', 'user2'])
|
||||
await config.option('percent.percent', 0).value.set(20)
|
||||
await config.option('percent.percent', 1).value.set(80)
|
||||
|
||||
run(main())
|
||||
```
|
23
docs/Makefile
Normal file
|
@ -0,0 +1,23 @@
|
|||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
SPHINXPROJ = pyfun
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
mkdir -p _build/html/_modules
|
||||
make -C ../src all
|
||||
|
4
docs/_static/css/custom.css
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
.wy-table-responsive table td {
|
||||
white-space: normal;
|
||||
}
|
||||
|
BIN
docs/_static/python-logo-large.png
vendored
Normal file
After Width: | Height: | Size: 13 KiB |
93
docs/api_global_permissives.rst
Normal file
|
@ -0,0 +1,93 @@
|
|||
==================
|
||||
Global permissive
|
||||
==================
|
||||
|
||||
Permissives allow access, during a calculation, to a normally unavailable variable.
|
||||
|
||||
In the :doc:api_property example we add a new `create` option that has a calculation with the option `exists` as parameter.
|
||||
|
||||
This option has a calculated default_multi value. If the file exists (so `exists` option is True) we don't want create automaticly the file:
|
||||
|
||||
.. literalinclude:: src/api_global_permissive.py
|
||||
:lines: 7-8
|
||||
:linenos:
|
||||
|
||||
Here is the new option:
|
||||
|
||||
.. literalinclude:: src/api_global_permissive.py
|
||||
:lines: 20-23
|
||||
:linenos:
|
||||
|
||||
:download:`download the config <src/api_global_permissive.py>`
|
||||
|
||||
Get/add/pop/reset global permissive
|
||||
=====================================
|
||||
|
||||
Let's try this config:
|
||||
|
||||
>>> config.property.read_write()
|
||||
>>> config.option('new.filename').value.set(['/etc', '/unknown'])
|
||||
>>> config.value.get()
|
||||
{'new.filename': ['/etc', '/unknown'], 'new.exists': [True, False], 'new.create': [False, True]}
|
||||
|
||||
Now we want to see `advanced` option. But how calculate create value?
|
||||
|
||||
>>> config.property.add('advanced')
|
||||
>>> try:
|
||||
... config.value.get()
|
||||
... except ConfigError as err:
|
||||
... print(err)
|
||||
unable to carry out a calculation for "Create automaticly the file", cannot access to option "This file exists" because has property "advanced"
|
||||
|
||||
We just have to add `advanced` permissive to allow calculation:
|
||||
|
||||
>>> config.permissive.add('advanced')
|
||||
>>> config.value.get()
|
||||
{'new.filename': ['/etc', '/unknown'], 'new.create': [False, True]}
|
||||
|
||||
At any time we can retrieve all global permissive:
|
||||
|
||||
>>> config.permissive.get()
|
||||
frozenset({'hidden', 'advanced'})
|
||||
|
||||
We can remove on permissive:
|
||||
|
||||
>>> config.permissive.pop('hidden')
|
||||
>>> config.permissive.get()
|
||||
frozenset({'advanced'})
|
||||
|
||||
And finally we can reset all permissives:
|
||||
|
||||
>>> config.permissive.reset()
|
||||
>>> config.permissive.get()
|
||||
frozenset()
|
||||
|
||||
Default permissives
|
||||
============================
|
||||
|
||||
Tiramisu estimate default permissive.
|
||||
|
||||
All properties added in `read write` mode and removed in `read only` mode are, by default, included in permissive list when we change mode:
|
||||
|
||||
>>> default = config.property.getdefault('read_write', 'append')
|
||||
>>> config.property.setdefault(frozenset(default | {'advanced'}), 'read_write', 'append')
|
||||
>>> default = config.property.getdefault('read_only', 'remove')
|
||||
>>> config.property.setdefault(frozenset(default | {'advanced'}), 'read_only', 'remove')
|
||||
>>> config.property.read_write()
|
||||
>>> config.permissive.get()
|
||||
frozenset({'advanced', 'hidden'})
|
||||
|
||||
Importation and exportation
|
||||
================================
|
||||
|
||||
In config, all permissive (global's and option's permissives) can be exportated:
|
||||
|
||||
>>> config.permissive.exportation()
|
||||
{None: frozenset({'hidden', 'advanced'})}
|
||||
|
||||
And reimported later:
|
||||
|
||||
>>> export = config.permissive.exportation()
|
||||
>>> config.permissive.importation(export)
|
||||
|
||||
.. note:: The exportation format is not stable and can be change later, please do not use importation otherwise than jointly with exportation.
|
185
docs/api_global_properties.rst
Normal file
|
@ -0,0 +1,185 @@
|
|||
================
|
||||
Global property
|
||||
================
|
||||
|
||||
Before start, have a look to :doc:property.
|
||||
|
||||
Let's start by import needed objects:
|
||||
|
||||
.. literalinclude:: src/api_global_property.py
|
||||
:lines: 1-4
|
||||
:linenos:
|
||||
|
||||
Instanciate a first option with `mandatory` property:
|
||||
|
||||
.. literalinclude:: src/api_global_property.py
|
||||
:lines: 7-10
|
||||
:linenos:
|
||||
|
||||
Instanciate a second option with calculated value, which verify if the file exists.
|
||||
|
||||
This options has `frozen`, `force_default_on_freeze` and `advanced` properties:
|
||||
|
||||
.. literalinclude:: src/api_global_property.py
|
||||
:lines: 11-15
|
||||
:linenos:
|
||||
|
||||
This two options are in a leadership:
|
||||
|
||||
.. literalinclude:: src/api_global_property.py
|
||||
:lines: 16-18
|
||||
:linenos:
|
||||
|
||||
Finally create the root option description and the config:
|
||||
|
||||
.. literalinclude:: src/api_global_property.py
|
||||
:lines: 19-20
|
||||
:linenos:
|
||||
|
||||
:download:`download the config <src/api_global_property.py>`
|
||||
|
||||
Read only and read write
|
||||
==========================
|
||||
|
||||
By default, there is no restriction.
|
||||
|
||||
For example, it's possible to change value of a `frozen` option (here the `exists`' option):
|
||||
|
||||
>>> config.option('new.filename').value.set(['/etc'])
|
||||
>>> config.option('new.exists', 0).value.set(False)
|
||||
|
||||
To have the good properties in "read / write" mode:
|
||||
|
||||
>>> config.property.read_write()
|
||||
>>> config.option('new.filename').value.set(['/etc'])
|
||||
>>> try:
|
||||
... config.option('new.exists', 0).value.set(False)
|
||||
...except PropertiesOptionError as err:
|
||||
... print(err)
|
||||
cannot modify the option "This file exists" because has property "frozen"
|
||||
|
||||
The read write mode is used be a human who wants to modify the configuration.
|
||||
Some variables are not displayed, because this person cannot modified it.
|
||||
|
||||
To have the good properties in "read only" mode:
|
||||
|
||||
>>> config.property.read_only()
|
||||
>>> try:
|
||||
... config.option('new.filename').value.set(['/etc'])
|
||||
...except PropertiesOptionError as err:
|
||||
... print(err)
|
||||
cannot modify the option "Filename" because has property "frozen"
|
||||
|
||||
In this mode it is impossible to modify the values of the options.
|
||||
It should be use by a script, for build a template, ...
|
||||
All variables not desactived are accessible.
|
||||
|
||||
Get/add/pop/reset global property
|
||||
=======================================
|
||||
|
||||
The default read only and read write properties are:
|
||||
|
||||
>>> config.property.read_only()
|
||||
>>> config.property.get()
|
||||
frozenset({'force_store_value', 'validator', 'everything_frozen', 'warnings', 'cache', 'mandatory', 'frozen', 'empty', 'disabled'})
|
||||
>>> config.property.read_write()
|
||||
>>> config.property.get()
|
||||
frozenset({'frozen', 'cache', 'warnings', 'disabled', 'validator', 'force_store_value', 'hidden'})
|
||||
|
||||
In the current config, the option has property `advanced`.
|
||||
|
||||
Has you can see below, the `advanced` is not used in any mode. This property doesn't affect Tiramisu.
|
||||
|
||||
Imagine that you don't want to see any advanced option by default. Just add this property in global property:
|
||||
|
||||
>>> config.option('new.filename').value.set(['/etc'])
|
||||
>>> config.property.read_write()
|
||||
>>> config.value.get()
|
||||
{'new.filename': ['/etc'], 'new.exists': [True]}
|
||||
>>> config.property.add('advanced')
|
||||
>>> config.property.get()
|
||||
frozenset({'frozen', 'advanced', 'hidden', 'validator', 'force_store_value', 'disabled', 'cache', 'warnings'})
|
||||
>>> config.value.get()
|
||||
{'new.filename': ['/etc']}
|
||||
|
||||
Of course you want to access to this option in read only mode.
|
||||
So you have to remove this property:
|
||||
|
||||
>>> config.property.read_only()
|
||||
>>> config.property.pop('advanced')
|
||||
>>> config.property.get()
|
||||
frozenset({'force_store_value', 'everything_frozen', 'frozen', 'warnings', 'empty', 'disabled', 'mandatory', 'cache', 'validator'})
|
||||
>>> config.value.get()
|
||||
{'new.filename': ['/etc'], 'new.exists': [True]}
|
||||
|
||||
At any time we can return to the default property (default means initialized properties, before change to read only or read write mode):
|
||||
|
||||
>>> config.property.read_only()
|
||||
>>> config.property.get()
|
||||
frozenset({'empty', 'cache', 'force_store_value', 'everything_frozen', 'warnings', 'frozen', 'disabled', 'mandatory', 'validator'})
|
||||
>>> config.property.reset()
|
||||
>>> config.property.get()
|
||||
frozenset({'cache', 'warnings', 'validator'})
|
||||
|
||||
Get default properties in mode
|
||||
=======================================
|
||||
|
||||
Add or pop properties each time we pass from one mode to an other is not a good idea. It better to change `read_write` and `read_only` mode directly.
|
||||
|
||||
Change mode means, in fact, add some properties and remove some other properties.
|
||||
|
||||
For example, when we pass to read_write mode, this properties are added:
|
||||
|
||||
>>> config.property.getdefault('read_write', 'append')
|
||||
frozenset({'disabled', 'validator', 'force_store_value', 'hidden', 'frozen'})
|
||||
|
||||
and this properties are remove:
|
||||
|
||||
>>> config.property.getdefault('read_write', 'remove')
|
||||
frozenset({'empty', 'everything_frozen', 'mandatory', 'permissive'})
|
||||
|
||||
Here is properties added when pass to read_only mode:
|
||||
|
||||
>>> config.property.getdefault('read_only', 'append')
|
||||
frozenset({'empty', 'mandatory', 'validator', 'disabled', 'force_store_value', 'everything_frozen', 'frozen'})
|
||||
|
||||
and this properties are remove:
|
||||
|
||||
>>> config.property.getdefault('read_only', 'remove')
|
||||
frozenset({'hidden', 'permissive'})
|
||||
|
||||
Just add the property to the default value to automatically automate the addition and deletion.
|
||||
We want to add the property when we switch to "read write" mode and automatically delete this property when we switch to "read only" mode:
|
||||
|
||||
>>> default = config.property.getdefault('read_write', 'append')
|
||||
>>> config.property.setdefault(frozenset(default | {'advanced'}), 'read_write', 'append')
|
||||
>>> default = config.property.getdefault('read_only', 'remove')
|
||||
>>> config.property.setdefault(frozenset(default | {'advanced'}), 'read_only', 'remove')
|
||||
|
||||
Let's try:
|
||||
|
||||
>>> 'advanced' in config.property.get()
|
||||
False
|
||||
>>> config.property.read_write()
|
||||
>>> 'advanced' in config.property.get()
|
||||
True
|
||||
>>> config.property.read_only()
|
||||
>>> 'advanced' in config.property.get()
|
||||
False
|
||||
|
||||
Importation and exportation
|
||||
=======================================
|
||||
|
||||
In config, all properties (global's and option's properties) can be exportated:
|
||||
|
||||
>>> config.property.exportation()
|
||||
{None: frozenset({'empty', 'cache', 'warnings', 'validator', 'disabled', 'force_store_value', 'everything_frozen', 'frozen', 'mandatory'})}
|
||||
|
||||
And reimported later:
|
||||
|
||||
>>> export = config.property.exportation()
|
||||
>>> config.property.importation(export)
|
||||
|
||||
.. note:: The exportation format is not stable and can be change later, please do not use importation otherwise than jointly with exportation.
|
||||
|
||||
|
17
docs/api_option_permissive.rst
Normal file
|
@ -0,0 +1,17 @@
|
|||
=====================
|
||||
Option's permissive
|
||||
=====================
|
||||
|
||||
|
||||
.. FIXME advanced in permissive
|
||||
|
||||
|
||||
.. FIXME unrestraint, forcepermissive
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
403
docs/api_option_property.rst
Normal file
|
@ -0,0 +1,403 @@
|
|||
==================
|
||||
Option's property
|
||||
==================
|
||||
|
||||
Let's start to build a config.
|
||||
|
||||
First of, import needed object:
|
||||
|
||||
.. literalinclude:: src/api_option_property.py
|
||||
:lines: 1-9
|
||||
:linenos:
|
||||
|
||||
Instanciate a first option to call a file name:
|
||||
|
||||
.. literalinclude:: src/api_option_property.py
|
||||
:lines: 56-59
|
||||
:linenos:
|
||||
|
||||
Secondly add an `exists` option to know if this file is already created:
|
||||
|
||||
.. literalinclude:: src/api_option_property.py
|
||||
:lines: 60-64
|
||||
:linenos:
|
||||
|
||||
Thirdly add a `create` option used by a potential script to create wanted file:
|
||||
|
||||
.. literalinclude:: src/api_option_property.py
|
||||
:lines: 65-72
|
||||
:linenos:
|
||||
|
||||
A new option is create to known the file type. If file already exists, retrieve automaticly the type, otherwise ask to the user:
|
||||
|
||||
.. literalinclude:: src/api_option_property.py
|
||||
:lines: 35-42
|
||||
:linenos:
|
||||
|
||||
.. literalinclude:: src/api_option_property.py
|
||||
:lines: 73-87
|
||||
:linenos:
|
||||
|
||||
In same model, create a `user` and `group` name options:
|
||||
|
||||
.. literalinclude:: src/api_option_property.py
|
||||
:lines: 12-32
|
||||
:linenos:
|
||||
|
||||
.. literalinclude:: src/api_option_property.py
|
||||
:lines: 88-111
|
||||
:linenos:
|
||||
|
||||
Finally create a `mode` option:
|
||||
|
||||
.. literalinclude:: src/api_option_property.py
|
||||
:lines: 45-53
|
||||
:linenos:
|
||||
|
||||
.. literalinclude:: src/api_option_property.py
|
||||
:lines: 112-116
|
||||
:linenos:
|
||||
|
||||
Let's build the config:
|
||||
|
||||
.. literalinclude:: src/api_option_property.py
|
||||
:lines: 118-124
|
||||
:linenos:
|
||||
|
||||
:download:`download the config <src/api_option_property.py>`
|
||||
|
||||
Get/add/pop/reset property
|
||||
=================================
|
||||
|
||||
option description's property
|
||||
'''''''''''''''''''''''''''''''''''''''''
|
||||
|
||||
An option description is an option. It's possible to set property to it:
|
||||
|
||||
>>> config.property.read_write()
|
||||
>>> config.option('new').property.get()
|
||||
set()
|
||||
|
||||
To add a property:
|
||||
|
||||
>>> config.option('new').property.add('disabled')
|
||||
>>> config.option('new').property.get()
|
||||
set('disabled')
|
||||
|
||||
The property affect the option description:
|
||||
|
||||
>>> try:
|
||||
... config.option('new').value.get()
|
||||
... except PropertiesOptionError as err:
|
||||
... print(err)
|
||||
cannot access to optiondescription "Add new file" because has property "disabled"
|
||||
|
||||
But, of course the child option too. If access to option description is not possible, it's not possible to child option too:
|
||||
|
||||
>>> try:
|
||||
... config.option('new.filename').value.get()
|
||||
... except PropertiesOptionError as err:
|
||||
... print(err)
|
||||
cannot access to optiondescription "Add new file" because has property "disabled"
|
||||
|
||||
We can remove an existed property too:
|
||||
|
||||
>>> config.option('new').property.add('hidden')
|
||||
>>> config.option('new').property.get()
|
||||
{'hidden', 'disabled'}
|
||||
>>> config.option('new').property.pop('hidden')
|
||||
>>> config.option('new').property.get()
|
||||
{'disabled'}
|
||||
|
||||
|
||||
It's possible to reset property:
|
||||
|
||||
>>> config.option('new').property.reset()
|
||||
>>> config.option('new').value.get()
|
||||
{'filename': [], 'exists': [], 'create': [], 'type': [], 'user': [], 'group': [], 'mode': []}
|
||||
|
||||
option's property
|
||||
'''''''''''''''''''''''''''''''''''''''
|
||||
|
||||
In a simple option we can add, pop or reset property:
|
||||
|
||||
>>> config.property.read_write()
|
||||
>>> config.option('new.filename').property.get())
|
||||
{'mandatory', 'unique', 'empty'}
|
||||
>>> config.option('new.filename').property.add('frozen')
|
||||
>>> config.option('new.filename').property.get()
|
||||
{'mandatory', 'unique', 'frozen', 'empty'}
|
||||
>>> config.option('new.filename').property.pop('empty')
|
||||
>>> config.option('new.filename').property.get()
|
||||
{'frozen', 'mandatory', 'unique'}
|
||||
>>> config.option('new.filename').property.reset()
|
||||
>>> config.option('new.filename').property.get()
|
||||
{'mandatory', 'unique', 'empty'}
|
||||
|
||||
leader's property
|
||||
''''''''''''''''''''''''''''
|
||||
|
||||
In leader's option can only have a list of property. For other's property, please set directly in leadership option:
|
||||
|
||||
>>> config.property.read_write()
|
||||
>>> try:
|
||||
... config.option('new.filename').property.add('hidden')
|
||||
... except LeadershipError as err:
|
||||
... print(err)
|
||||
leader cannot have "hidden" property
|
||||
>>> config.option('new').property.add('hidden')
|
||||
|
||||
This `hidden` property has to affect leader option but also all follower option.
|
||||
That why you have to set this kind of properties directly in leadership option.
|
||||
|
||||
.. note::
|
||||
Allowed properties for a leader: 'empty', 'unique', 'force_store_value', 'mandatory', 'force_default_on_freeze', 'force_metaconfig_on_freeze', and 'frozen'.
|
||||
|
||||
|
||||
follower's property
|
||||
'''''''''''''''''''''''''''''''''''''''
|
||||
|
||||
First of add, add values in leader option:
|
||||
|
||||
>>> config.property.read_write()
|
||||
>>> config.option('new.filename').value.set(['/etc/passwd', 'unknown1', 'unknown2'])
|
||||
|
||||
We have to get property with an index:
|
||||
|
||||
>>> config.option('new.create', 1).property.get()
|
||||
set()
|
||||
|
||||
We can set property with index:
|
||||
|
||||
>>> config.option('new.create', 1).property.add('frozen')
|
||||
>>> config.option('new.create', 1).property.get()
|
||||
{'frozen'}
|
||||
>>> config.option('new.create', 2).property.get()
|
||||
set()
|
||||
|
||||
But we can alse set without index (available for all follower's value):
|
||||
|
||||
>>> config.option('new.create').property.add('frozen')
|
||||
>>> print(config.option('new.create', 1).property.get())
|
||||
{'frozen'}
|
||||
>>> print(config.option('new.create', 2).property.get())
|
||||
{'frozen'}
|
||||
|
||||
Calculated property
|
||||
=======================
|
||||
|
||||
A property can be a :doc:`calculation`. That means that the property will be set or not following the context.
|
||||
|
||||
The Calculation can return two type of value:
|
||||
|
||||
- a `str` this string is a new property
|
||||
- `None` so this property is cancel
|
||||
|
||||
First of all, have a look to the `create` properties:
|
||||
|
||||
.. literalinclude:: src/api_option_property.py
|
||||
:lines: 68-72
|
||||
:linenos:
|
||||
|
||||
This option has only one property which is `disabled` when `exists` has value True.
|
||||
|
||||
If the file exists, we don't have to now if user wants create it. It is already exists. So we don't have to access to this option.
|
||||
|
||||
Secondly, have a look to the `type` properties:
|
||||
|
||||
.. literalinclude:: src/api_option_property.py
|
||||
:lines: 79-87
|
||||
:linenos:
|
||||
|
||||
There is:
|
||||
|
||||
- two static properties: `force_default_on_freeze` and `mandatory`.
|
||||
- two calculated properties: `hidden` and `frozen`
|
||||
|
||||
If the file is already exists, the two calculated properties are present to this option.
|
||||
|
||||
So we can access to this option only in read only mode and user cannot modified it's value.
|
||||
|
||||
Finally have a look to the `username` and `grpname` options' properties:
|
||||
|
||||
.. literalinclude:: src/api_option_property.py
|
||||
:lines: 94-99
|
||||
:linenos:
|
||||
|
||||
In this case we have two properties:
|
||||
|
||||
- one static property: `force_store_value`
|
||||
- one calculated property: `mandatory`
|
||||
|
||||
This calculated property is apply only if `create` is True.
|
||||
|
||||
Be carefull to the `create` option. It could be disabled, so not accessible in calculation if the file exists as see previously.
|
||||
|
||||
That why we add notraisepropertyerror attribute to True, even if the calculation will failed.
|
||||
In this case the value of `create` is not add in `calc_value` argument.
|
||||
|
||||
In this case the function `calc_value` consider that the property `mandatory` has to be set.
|
||||
|
||||
But we just want to set `mandatory` property only if create is False. That why we add the no_condition_is_invalid to True.
|
||||
|
||||
Force the registration of a value
|
||||
====================================
|
||||
|
||||
The property `force_store_value` is a special property. This property permit to store a value automaticly even if user do not set value or reset the value.
|
||||
This is useful especially, for example, for recording a random draw password through a calculation. Or to store any first result for a calculation.
|
||||
|
||||
To the, create a new config:
|
||||
|
||||
>>> config = Config(root)
|
||||
|
||||
If we add value in `filename`, the option `exists` stay a default value, but not the `mode` option, which has `force_store_value`:
|
||||
|
||||
>>> config.property.read_write()
|
||||
>>> config.option('new.filename').value.set(['/etc'])
|
||||
>>> print(config.option('new.filename').owner.get())
|
||||
user
|
||||
>>> print(config.option('new.exists', 0).owner.get())
|
||||
default
|
||||
>>> print(config.option('new.mode', 0).owner.get())
|
||||
forced
|
||||
|
||||
If we try to reset `mode` value, this option is modified:
|
||||
|
||||
>>> config.option('new.mode', 0).value.reset()
|
||||
>>> config.option('new.mode', 0).owner.get()
|
||||
forced
|
||||
|
||||
Non-empty value, mandatory and unique
|
||||
========================================================
|
||||
|
||||
Leader and multi have automaticly two properties `unique` and `empty`:
|
||||
|
||||
>>> config = Config(OptionDescription('root', 'root', [FilenameOption('filename',
|
||||
... 'Filename',
|
||||
... multi=True)]))
|
||||
>>> config.option('filename').property.get()
|
||||
{'empty', 'unique'}
|
||||
|
||||
To remove `empty` property
|
||||
|
||||
>>> config = Config(OptionDescription('root', 'root', [FilenameOption('filename',
|
||||
... 'Filename',
|
||||
... properties=('notempty',),
|
||||
... multi=True)]))
|
||||
>>> config.option('filename').property.get()
|
||||
{'unique'}
|
||||
>>> config = Config(OptionDescription('root', 'root', [FilenameOption('filename',
|
||||
... 'Filename',
|
||||
... properties=('notunique',),
|
||||
... multi=True)]))
|
||||
>>> config.option('filename').property.get()
|
||||
{'empty'}
|
||||
|
||||
Let's try with previous config.
|
||||
|
||||
First of all we remove `force_store_value` mode:
|
||||
|
||||
>>> config = Config(root)
|
||||
>>> properties = config.property.getdefault('read_write', 'append') - {'force_store_value'}
|
||||
>>> config.property.setdefault(frozenset(properties), 'read_write', 'append')
|
||||
>>> properties = config.property.getdefault('read_only', 'append') - {'force_store_value'}
|
||||
>>> config.property.setdefault(frozenset(properties), 'read_only', 'append')
|
||||
|
||||
In addition to the specified `mandatory` property, leader have automaticly two properties: `unique` and `empty`:
|
||||
|
||||
>>> config.option('new.filename').property.get()
|
||||
{'unique', 'mandatory', 'empty'}
|
||||
|
||||
What is the difference between the property `unique` and `mandatory`?
|
||||
|
||||
Let's try with no value at all:
|
||||
|
||||
>>> config.property.read_only()
|
||||
>>> try:
|
||||
... config.option('new.filename').value.get()
|
||||
>>> except PropertiesOptionError as err:
|
||||
... print(err)
|
||||
cannot access to option "Filename" because has property "mandatory"
|
||||
|
||||
A `mandatory` multi must have at least one value. This value is check only in read only mode.
|
||||
|
||||
If we remove the `mandatory` property, the value is valid:
|
||||
|
||||
>>> config.property.read_write()
|
||||
>>> config.option('new.filename').property.pop('mandatory')
|
||||
>>> config.option('new.filename').property.get()
|
||||
{'unique', 'empty'}
|
||||
>>> config.property.read_only()
|
||||
>>> config.option('new.filename').value.get()
|
||||
[]
|
||||
|
||||
A `empty` multi can has no value, but if you set a value, it must not be None:
|
||||
|
||||
>>> config.property.read_write()
|
||||
>>> config.option('new.filename').value.set(['/etc', None])
|
||||
>>> config.property.read_only()
|
||||
>>> try:
|
||||
... config.option('new.filename').value.get()
|
||||
... except PropertiesOptionError as err:
|
||||
... print(err)
|
||||
cannot access to option "Filename" because has property "empty"
|
||||
|
||||
Trying now without this property:
|
||||
|
||||
>>> config.property.read_write()
|
||||
>>> config.option('new.filename').property.pop('empty')
|
||||
>>> config.option('new.filename').value.set(['/etc', None])
|
||||
>>> config.property.read_only()
|
||||
>>> config.option('new.filename').value.get()
|
||||
['/etc', None]
|
||||
|
||||
A `unique` property in multi means you cannot have same value twice:
|
||||
|
||||
>>> config.property.read_write()
|
||||
>>> try:
|
||||
... config.option('new.filename').value.set(['/etc', '/etc'])
|
||||
... except ValueError as err:
|
||||
... print(err)
|
||||
"['/etc', '/etc']" is an invalid file name for "Filename", the value "/etc" is not unique
|
||||
|
||||
When removing this property:
|
||||
|
||||
>>> config.property.read_write()
|
||||
>>> config.option('new.filename').property.pop('unique')
|
||||
>>> config.option('new.filename').value.set(['/etc', '/etc'])
|
||||
>>> config.property.read_only()
|
||||
>>> config.option('new.filename').value.get()
|
||||
['/etc', '/etc']
|
||||
|
||||
Non-modifiable option
|
||||
=====================
|
||||
|
||||
Freeze an option means that you cannot change the value of this option:
|
||||
|
||||
>>> config = Config(root)
|
||||
>>> config.property.read_write()
|
||||
>>> config.option('new.filename').value.set(['unknown'])
|
||||
>>> config.option('new.create', 0).value.set(False)
|
||||
>>> config.option('new.create', 0).property.add('frozen')
|
||||
>>> try:
|
||||
... config.option('new.create', 0).value.set(False)
|
||||
... except PropertiesOptionError as err:
|
||||
... print(err)
|
||||
cannot modify the option "Create automaticly the file" because has property "frozen"
|
||||
|
||||
Sometime (for example when an option is calculated) we want retrieve the default value (so the calculated value) when we add `frozen` option.
|
||||
|
||||
In the current example, `new.exists` is a calculated value and we don't want that the used modify this option. So we add `frozen` and `force_default_on_freeze` properties.
|
||||
|
||||
For example, without mode, we can modify the `new.exists` option, but in `read_only` mode, we want to have default value:
|
||||
|
||||
>>> config = Config(root)
|
||||
>>> config.option('new.filename').value.set(['unknown'])
|
||||
>>> config.option('new.exists', 0).value.set(True)
|
||||
>>> config.option('new.exists', 0).value.get()
|
||||
True
|
||||
>>> config.property.read_write()
|
||||
>>> config.option('new.exists', 0).value.get()
|
||||
False
|
||||
|
||||
The property `force_default_on_freeze` is also avalaible in the option `new.type`. If the file exists, the type is calculated but if it not already exists, the user needs to set the correct wanted type.
|
31
docs/api_property.rst
Normal file
|
@ -0,0 +1,31 @@
|
|||
==================================
|
||||
Playing with property
|
||||
==================================
|
||||
|
||||
Properties and permissives affect the Tiramisu behaviour.
|
||||
|
||||
This mechanism makes available or not other options. It also controls the behavior of options.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
api_global_properties
|
||||
api_global_permissives
|
||||
api_option_property
|
||||
api_option_permissive
|
||||
|
||||
|
||||
|
||||
.. class TiramisuContextValue(TiramisuConfig):
|
||||
.. def mandatory(self):
|
||||
.. """Return path of options with mandatory property without any value"""
|
||||
..
|
||||
|
||||
.. class TiramisuOptionPermissive(CommonTiramisuOption):
|
||||
.. """Manage option's permissive"""
|
||||
.. def get(self):
|
||||
.. """Get permissives value"""
|
||||
.. def set(self, permissives):
|
||||
.. """Set permissives value"""
|
||||
.. def reset(self):
|
||||
.. """Reset all personalised permissive"""
|
|
@ -1,8 +1,12 @@
|
|||
# Manage values
|
||||
==================================
|
||||
Manage values
|
||||
==================================
|
||||
|
||||
## Values with options
|
||||
Values with options
|
||||
=========================
|
||||
|
||||
### Simple option
|
||||
Simple option
|
||||
----------------------------
|
||||
|
||||
Begin by creating a Config. This Config will contains two options:
|
||||
|
||||
|
@ -11,129 +15,92 @@ Begin by creating a Config. This Config will contains two options:
|
|||
|
||||
Let's import needed object:
|
||||
|
||||
```python
|
||||
from asyncio import run
|
||||
from shutil import disk_usage
|
||||
from os.path import isdir
|
||||
from tiramisu import FilenameOption, FloatOption, OptionDescription, Config, \
|
||||
Calculation, Params, ParamValue, ParamOption, ParamSelfOption
|
||||
```
|
||||
.. literalinclude:: src/api_value.py
|
||||
:lines: 1-4
|
||||
:linenos:
|
||||
|
||||
Create a function that verify the path exists in current system:
|
||||
|
||||
```python
|
||||
def valid_is_dir(path):
|
||||
# verify if path is a directory
|
||||
if not isdir(path):
|
||||
raise ValueError('this directory does not exist')
|
||||
```
|
||||
.. literalinclude:: src/api_value.py
|
||||
:lines: 6-9
|
||||
:linenos:
|
||||
|
||||
Use this function as a :doc:`validator` in a new option call `path`:
|
||||
|
||||
```python
|
||||
filename = FilenameOption('path', 'Path', validators=[Calculation(valid_is_dir,
|
||||
Params(ParamSelfOption()))])
|
||||
```
|
||||
.. literalinclude:: src/api_value.py
|
||||
:lines: 24-25
|
||||
:linenos:
|
||||
|
||||
Create a second function that calculate the disk usage:
|
||||
|
||||
```python
|
||||
def calc_disk_usage(path, size='bytes'):
|
||||
# do not calc if path is None
|
||||
if path is None:
|
||||
return None
|
||||
if size == 'bytes':
|
||||
div = 1
|
||||
else:
|
||||
# bytes to gigabytes
|
||||
div = 1024 * 1024 * 1024
|
||||
return disk_usage(path).free / div
|
||||
```
|
||||
.. literalinclude:: src/api_value.py
|
||||
:lines: 11-21
|
||||
:linenos:
|
||||
|
||||
Add a new option call `usage` that use this function with first argument the option `path` created before:
|
||||
|
||||
```python
|
||||
usage = FloatOption('usage', 'Disk usage', Calculation(calc_disk_usage,
|
||||
Params(ParamOption(filename))))
|
||||
```
|
||||
.. literalinclude:: src/api_value.py
|
||||
:lines: 26-27
|
||||
:linenos:
|
||||
|
||||
Finally add those options in option description and a Config:
|
||||
|
||||
```python
|
||||
disk = OptionDescription('disk', 'Verify disk usage', [filename, usage])
|
||||
root = OptionDescription('root', 'root', [disk])
|
||||
async def main():
|
||||
config = await Config(root)
|
||||
await config.property.read_write()
|
||||
return config
|
||||
.. literalinclude:: src/api_value.py
|
||||
:lines: 28-31
|
||||
:linenos:
|
||||
|
||||
config = run(main())
|
||||
```
|
||||
:download:`download the config <src/api_value.py>`
|
||||
|
||||
#### Get and set a value
|
||||
Get and set a value
|
||||
'''''''''''''''''''''''''''''
|
||||
|
||||
First of all, retrieve the values of both options:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
print(await config.option('disk.path').value.get())
|
||||
print(await config.option('disk.usage').value.get())
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
>>> config.option('disk.path').value.get()
|
||||
None
|
||||
>>> config.option('disk.usage').value.get()
|
||||
None
|
||||
```
|
||||
|
||||
Enter a value of the `path` option:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
await config.option('disk.path').value.set('/')
|
||||
print(await config.option('disk.path').value.get())
|
||||
print(await config.option('disk.usage').value.get())
|
||||
>>> config.option('disk.path').value.set('/')
|
||||
|
||||
run(main())
|
||||
```
|
||||
The value is really change:
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
>>> config.option('disk.path').value.get()
|
||||
/
|
||||
|
||||
Now, calculation retrieve a value:
|
||||
|
||||
>>> config.option('disk.usage').value.get()
|
||||
668520882176.0
|
||||
```
|
||||
|
||||
When you enter a value it is validated:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
try:
|
||||
await config.option('disk.path').value.set('/unknown')
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
>>> try:
|
||||
>>> config.option('disk.path').value.set('/unknown')
|
||||
>>> except ValueError as err:
|
||||
>>> print(err)
|
||||
"/unknown" is an invalid file name for "Path", this directory does not exist
|
||||
```
|
||||
|
||||
#### Is value is valid?
|
||||
We can also set a :doc:`calculation` as value. For example, we want to launch previous function but with in_gb to True as second argument:
|
||||
|
||||
>>> calc = Calculation(calc_disk_usage, Params((ParamOption(filename),
|
||||
... ParamValue('gigabytes'))))
|
||||
>>> config.option('disk.usage').value.set(calc)
|
||||
>>> config.option('disk.usage').value.get()
|
||||
622.6080360412598
|
||||
|
||||
Is value is valid?
|
||||
'''''''''''''''''''''''''''''
|
||||
|
||||
To check is a value is valid:
|
||||
|
||||
```python
|
||||
await config.option('disk.path').value.valid()
|
||||
```
|
||||
>>> config.option('disk.path').value.valid()
|
||||
True
|
||||
|
||||
#### Display the default value
|
||||
Display the default value
|
||||
'''''''''''''''''''''''''''''
|
||||
|
||||
Even if the value is modify, you can display the default value with `default` method:
|
||||
|
||||
|
@ -144,7 +111,8 @@ Even if the value is modify, you can display the default value with `default` me
|
|||
>>> config.option('disk.usage').value.default()
|
||||
668510105600.0
|
||||
|
||||
#### Return to the default value
|
||||
Return to the default value
|
||||
'''''''''''''''''''''''''''''
|
||||
|
||||
If the value is modified, just `reset` it to retrieve the default value:
|
||||
|
||||
|
@ -155,7 +123,8 @@ If the value is modified, just `reset` it to retrieve the default value:
|
|||
>>> config.option('disk.path').value.get()
|
||||
None
|
||||
|
||||
#### The ownership of a value
|
||||
The ownership of a value
|
||||
'''''''''''''''''''''''''''''
|
||||
|
||||
Every option has an owner, that will indicate who changed the option's value last.
|
||||
|
||||
|
@ -206,26 +175,28 @@ We can change this owner:
|
|||
>>> config.option('disk.path').owner.get()
|
||||
itsme
|
||||
|
||||
### Get choices from a Choice option
|
||||
Get choices from a Choice option
|
||||
--------------------------------------
|
||||
|
||||
In the previous example, it's difficult to change the second argument of the `calc_disk_usage`.
|
||||
|
||||
For ease the change, add a `ChoiceOption` and replace the `size_type` and `disk` option:
|
||||
|
||||
.. literalinclude:: ../src/api_value_choice.py
|
||||
.. literalinclude:: src/api_value_choice.py
|
||||
:lines: 26-31
|
||||
:linenos:
|
||||
|
||||
We set the default value to `bytes`, if not, the default value will be None.
|
||||
|
||||
:download:`download the config <../src/api_value_choice.py>`
|
||||
:download:`download the config <src/api_value_choice.py>`
|
||||
|
||||
At any time, we can get all de choices avalaible for an option:
|
||||
|
||||
>>> config.option('disk.size_type').value.list()
|
||||
('bytes', 'giga bytes')
|
||||
|
||||
### Value in multi option
|
||||
Value in multi option
|
||||
--------------------------------------
|
||||
|
||||
.. FIXME undefined
|
||||
|
||||
|
@ -237,25 +208,26 @@ First of all, we have to modification in this option:
|
|||
- add multi attribute to True
|
||||
- the function use in validation valid a single value, so each value in the list must be validate separatly, for that we add whole attribute to False in `ParamSelfOption` object
|
||||
|
||||
.. literalinclude:: ../src/api_value_multi.py
|
||||
.. literalinclude:: src/api_value_multi.py
|
||||
:lines: 23-25
|
||||
:linenos:
|
||||
|
||||
Secondly, the function calc_disk_usage must return a list:
|
||||
|
||||
.. literalinclude:: ../src/api_value_multi.py
|
||||
.. literalinclude:: src/api_value_multi.py
|
||||
:lines: 11-26
|
||||
:linenos:
|
||||
|
||||
Finally `usage` option is also a multi:
|
||||
|
||||
.. literalinclude:: ../src/api_value_multi.py
|
||||
.. literalinclude:: src/api_value_multi.py
|
||||
:lines: 27-30
|
||||
:linenos:
|
||||
|
||||
:download:`download the config <../src/api_value_multi.py>`
|
||||
:download:`download the config <src/api_value_multi.py>`
|
||||
|
||||
#### Get or set a multi value
|
||||
Get or set a multi value
|
||||
'''''''''''''''''''''''''''''
|
||||
|
||||
Since the options are multi, the default value is a list:
|
||||
|
||||
|
@ -272,7 +244,8 @@ A multi option waiting for a list:
|
|||
>>> config.option('disk.usage').value.get()
|
||||
[668499898368.0, 8279277568.0]
|
||||
|
||||
#### The ownership of multi option
|
||||
The ownership of multi option
|
||||
'''''''''''''''''''''''''''''
|
||||
|
||||
There is no difference in behavior between a simple option and a multi option:
|
||||
|
||||
|
@ -285,7 +258,8 @@ default
|
|||
>>> config.option('disk.path').owner.get()
|
||||
user
|
||||
|
||||
### Leadership
|
||||
Leadership
|
||||
--------------------------------------
|
||||
|
||||
In previous example, we cannot define different `size_type` for each path. If you want do this, you need a leadership.
|
||||
|
||||
|
@ -295,23 +269,24 @@ As each value of followers are isolate, the function `calc_disk_usage` will rece
|
|||
|
||||
So let's change this function:
|
||||
|
||||
.. literalinclude:: ../src/api_value_leader.py
|
||||
.. literalinclude:: src/api_value_leader.py
|
||||
:lines: 12-18
|
||||
:linenos:
|
||||
|
||||
Secondly the option `size_type` became a multi:
|
||||
|
||||
.. literalinclude:: ../src/api_value_leader.py
|
||||
.. literalinclude:: src/api_value_leader.py
|
||||
:lines: 24-25
|
||||
:linenos:
|
||||
|
||||
Finally disk has to be a leadership:
|
||||
|
||||
.. literalinclude:: ../src/api_value_leader.py
|
||||
.. literalinclude:: src/api_value_leader.py
|
||||
:lines: 30
|
||||
:linenos:
|
||||
|
||||
#### Get and set a leader
|
||||
Get and set a leader
|
||||
'''''''''''''''''''''''''''''
|
||||
|
||||
A leader is, in fact, a multi option:
|
||||
|
||||
|
@ -346,7 +321,8 @@ To reduce use the `pop` method:
|
|||
>>> config.option('disk.path').value.get()
|
||||
['/']
|
||||
|
||||
#### Get and set a follower
|
||||
Get and set a follower
|
||||
'''''''''''''''''''''''''''''
|
||||
|
||||
As followers are isolate, we cannot get all the follower values:
|
||||
|
||||
|
@ -376,7 +352,8 @@ As the leader, follower has a length (in fact, this is the leader's length):
|
|||
>>> config.option('disk.size_type').value.len()
|
||||
2
|
||||
|
||||
#### The ownership of a leader and follower
|
||||
The ownership of a leader and follower
|
||||
'''''''''''''''''''''''''''''''''''''''''''
|
||||
|
||||
There is no differences between a multi option and a leader option:
|
||||
|
||||
|
@ -396,37 +373,36 @@ True
|
|||
>>> config.option('disk.size_type', 1).owner.get()
|
||||
default
|
||||
|
||||
## Values in option description
|
||||
Values in option description
|
||||
==============================
|
||||
|
||||
With an option description we can have directly a dict with all option's name and value:
|
||||
|
||||
>>> config.option('disk.path').value.set(['/', '/tmp'])
|
||||
>>> config.option('disk.size_type', 0).value.set('giga bytes')
|
||||
>>> config.option('disk').value.dict()
|
||||
{'path': ['/', '/tmp'], 'size_type': ['giga bytes', 'bytes'], 'usage': [622.578239440918, 8279273472.0]}
|
||||
|
||||
An attribute fullpath permit to have fullpath of child option:
|
||||
|
||||
>>> config.option('disk').value.dict(fullpath=True)
|
||||
>>> config.option('disk').value.get()
|
||||
{'disk.path': ['/', '/tmp'], 'disk.size_type': ['giga bytes', 'bytes'], 'disk.usage': [622.578239440918, 8279273472.0]}
|
||||
|
||||
## Values in config
|
||||
Values in config
|
||||
==========================
|
||||
|
||||
###dict
|
||||
get
|
||||
--------
|
||||
|
||||
With the `config` we can have directly a dict with all option's name and value:
|
||||
|
||||
>>> config.option('disk.path').value.set(['/', '/tmp'])
|
||||
>>> config.option('disk.size_type', 0).value.set('giga bytes')
|
||||
>>> config.value.dict()
|
||||
>>> config.value.get()
|
||||
{'disk.path': ['/', '/tmp'], 'disk.size_type': ['giga bytes', 'bytes'], 'disk.usage': [622.578239440918, 8279273472.0]}
|
||||
|
||||
If you don't wan't path but only the name:
|
||||
|
||||
>>> config.value.dict(flatten=True)
|
||||
>>> config.value.get(flatten=True)
|
||||
{'path': ['/', '/tmp'], 'size_type': ['giga bytes', 'bytes'], 'usage': [622.578239440918, 8279273472.0]}
|
||||
|
||||
### importation/exportation
|
||||
importation/exportation
|
||||
------------------------
|
||||
|
||||
In config, we can export full values:
|
||||
|
||||
|
@ -439,4 +415,3 @@ and reimport it later:
|
|||
>>> config.value.importation(export)
|
||||
|
||||
.. note:: The exportation format is not stable and can be change later, please do not use importation otherwise than jointly with exportation.
|
||||
|
119
docs/application.rst
Normal file
|
@ -0,0 +1,119 @@
|
|||
==================================
|
||||
A full application
|
||||
==================================
|
||||
|
||||
The firefox network configuration
|
||||
-------------------------------------
|
||||
|
||||
Now we are going to resume everything we have seen with a concrete example.
|
||||
We're going to take an example based on the `Mozilla Firefox
|
||||
<https://www.mozilla.org/en-US/firefox/>`_ proxy's
|
||||
configuration, like what is required when you open the `network settings` in
|
||||
the General configuration's firefox page:
|
||||
|
||||
.. image:: images/firefox_preferences.png
|
||||
|
||||
The tiramisu's configuration
|
||||
----------------------------
|
||||
|
||||
Build the `Config`
|
||||
''''''''''''''''''''
|
||||
|
||||
First, let's create our options :
|
||||
|
||||
.. literalinclude:: src/application.py
|
||||
:lines: 1-3, 12-20
|
||||
:linenos:
|
||||
|
||||
This first option is the most important one : its value will determine which other options
|
||||
are disabled and which are not. The same thing will happen with other options later.
|
||||
Here are the others options we'll be using :
|
||||
|
||||
.. literalinclude:: src/application.py
|
||||
:lines: 23-221
|
||||
:linenos:
|
||||
|
||||
As you can see, we're using :doc:`value <api_value>`, :doc:`property <api_option_property>`
|
||||
and :doc:`calculation` in the setting of our options, because we have many options which
|
||||
value or accessibility is depending on the value of other options.
|
||||
|
||||
Now we need to create OptionDescriptions and configs :
|
||||
|
||||
.. literalinclude:: src/application.py
|
||||
:lines: 223-232
|
||||
:linenos:
|
||||
|
||||
Download the :download:`full code <src/application.py>` of this example.
|
||||
|
||||
As you can see, we regrouped a lot of options in 'protocols', so we can set a calculated `disabled` property
|
||||
that is apply to all those options. This way, we don't have to put the instruction on every option
|
||||
one by one.
|
||||
|
||||
Let's try
|
||||
'''''''''''''''
|
||||
|
||||
Now that we have our Config, it's time to run some tests !
|
||||
Here are a few code blocks you can test and the results you should get :
|
||||
|
||||
1. Automatic proxy configuration URL:
|
||||
|
||||
>>> proxy_config.property.read_write()
|
||||
>>> proxy_config.option('proxy_mode').value.set('Automatic proxy configuration URL')
|
||||
>>> proxy_config.option('auto_config_url').value.set('http://192.168.1.1/wpad.dat')
|
||||
>>> proxy_config.property.read_only()
|
||||
>>> for path, value in proxy_config.value.get().items():
|
||||
... print(proxy_config.option(path).option.doc() + ': "' + str(value) + '"')
|
||||
Proxy's config mode: "Automatic proxy configuration URL"
|
||||
Address for which proxy will be desactivated: "[]"
|
||||
Proxy's auto config URL: "http://192.168.1.1/wpad.dat"
|
||||
Prompt for authentication if password is saved: "False"
|
||||
Enable DNS over HTTPS: "False"
|
||||
|
||||
2. Auto-detect proxy settings for this network:
|
||||
|
||||
>>> proxy_config.property.read_write()
|
||||
>>> proxy_config.option('proxy_mode').value.set('Auto-detect proxy settings for this network')
|
||||
>>> proxy_config.option('no_proxy').value.set(['localhost',
|
||||
... '127.0.0.1',
|
||||
... '192.16.10.150',
|
||||
... '192.168.5.101',
|
||||
... '192.168.56.101/32',
|
||||
... '192.168.20.0/24',
|
||||
... '.tiramisu.org',
|
||||
... 'mozilla.org'])
|
||||
>>> proxy_config.option('dns_over_https.enable_dns_over_https').value.set(True)
|
||||
>>> proxy_config.option('dns_over_https.used_dns').value.set('default')
|
||||
>>> proxy_config.property.read_only()
|
||||
>>> for path, value in proxy_config.value.get().items():
|
||||
... print(proxy_config.option(path).option.doc() + ': "' + str(value) + '"')
|
||||
Proxy's config mode: "Auto-detect proxy settings for this network"
|
||||
Address for which proxy will be desactivated: "['localhost', '127.0.0.1', '192.16.10.150', '192.168.5.101', '192.168.56.101/32', '192.168.20.0/24', '.tiramisu.org', 'mozilla.org']"
|
||||
Prompt for authentication if password is saved: "False"
|
||||
Enable DNS over HTTPS: "True"
|
||||
Used DNS: "default"
|
||||
|
||||
Set use_for_all_protocols to True:
|
||||
|
||||
>>> proxy_config.property.read_write()
|
||||
>>> proxy_config.option('protocols.use_for_all_protocols').value.set(True)
|
||||
>>> proxy_config.property.read_only()
|
||||
>>> for path, value in proxy_config.value.get().items():
|
||||
... print(proxy_config.option(path).option.doc() + ': "' + str(value) + '"')
|
||||
Proxy's config mode: "Manual proxy configuration"
|
||||
Address: "192.168.20.1"
|
||||
Port: "8080"
|
||||
Use HTTP IP and Port for all protocols: "True"
|
||||
Address: "192.168.20.1"
|
||||
Port: "8080"
|
||||
Address: "192.168.20.1"
|
||||
Port: "8080"
|
||||
Address: "192.168.20.1"
|
||||
Port: "8080"
|
||||
SOCKS host version used by proxy: "v5"
|
||||
Address for which proxy will be desactivated: "[]"
|
||||
Prompt for authentication if password is saved: "False"
|
||||
Use Proxy DNS when using SOCKS v5: "False"
|
||||
Enable DNS over HTTPS: "True"
|
||||
Used DNS: "custom"
|
||||
Custom DNS URL: "https://dns-url.com"
|
||||
|
159
docs/browse.rst
Normal file
|
@ -0,0 +1,159 @@
|
|||
Browse the :class:`Config`
|
||||
===========================
|
||||
|
||||
Getting the options
|
||||
----------------------
|
||||
|
||||
.. note:: The :class:`Config` object we are using is located here in this script:
|
||||
|
||||
:download:`download the source <src/property.py>`
|
||||
|
||||
Let's retrieve the config object, named `cfg`
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from property import cfg
|
||||
|
||||
We retrieve by path an option named `var1`
|
||||
and then we retrieve its name and its docstring
|
||||
|
||||
.. code-block:: bash
|
||||
:emphasize-lines: 2, 5, 8
|
||||
|
||||
print(cfg.option('od1.var1'))
|
||||
<tiramisu.api.TiramisuOption object at 0x7f3876cc5940>
|
||||
|
||||
print(cfg.option('od1.var1').option.name())
|
||||
'var1'
|
||||
|
||||
print(cfg.option('od1.var1').option.doc())
|
||||
'first option'
|
||||
|
||||
|
||||
Accessing the values of the options
|
||||
-------------------------------------
|
||||
|
||||
Let's browse the configuration structure and option values.
|
||||
|
||||
You have getters as a `get` method on option objects:
|
||||
|
||||
|
||||
.. code-block:: bash
|
||||
:emphasize-lines: 10, 14
|
||||
|
||||
# getting all the options
|
||||
print(cfg.option.value.get())
|
||||
{'var1': None, 'var2': 'value'}
|
||||
|
||||
# getting the `od1` option description
|
||||
print(cfg.option('od1').value.get())
|
||||
{'od1.var1': None, 'od1.var2': 'value'}
|
||||
|
||||
# getting the var1 option's value
|
||||
print(cfg.option('od1.var1').value.get())
|
||||
None
|
||||
|
||||
# getting the var2 option's default value
|
||||
print(cfg.option('od1.var2').value.get())
|
||||
'value'
|
||||
|
||||
# trying to get a non existent option's value
|
||||
cfg.option('od1.idontexist').value.get()
|
||||
AttributeError: unknown option "idontexist" in optiondescription "od1"
|
||||
|
||||
|
||||
Setting the value of an option
|
||||
------------------------------
|
||||
|
||||
An important part of the setting's configuration consists of setting the
|
||||
value's option.
|
||||
|
||||
|
||||
You have setters as a `set` method on option objects.
|
||||
|
||||
And if you wanna come back to a default value, use the `reset()` method.
|
||||
|
||||
.. code-block:: bash
|
||||
:emphasize-lines: 2
|
||||
|
||||
# changing the `od1.var1` value
|
||||
cfg.option('od1.var1').value.set('éééé')
|
||||
print(cfg.option('od1.var1').value.get())
|
||||
'éééé'
|
||||
|
||||
# carefull to the type of the value to be set
|
||||
cfg.option('od1.var1').value.set(23454)
|
||||
ValueError: "23454" is an invalid string for "first variable"
|
||||
|
||||
# let's come back to the default value
|
||||
cfg.option('od1.var2').value.reset()
|
||||
print(cfg.option('od1.var2').value.get())
|
||||
'value'
|
||||
|
||||
.. important:: If the config is `read only`, setting an option's value isn't allowed, see :doc:`property`
|
||||
|
||||
|
||||
Let's make the protocol of accessing a `Config`'s option explicit
|
||||
(because explicit is better than implicit):
|
||||
|
||||
1. If the option has not been declared, an `Error` is raised,
|
||||
2. If an option is declared, but neither a value nor a default value has
|
||||
been set, the returned value is `None`,
|
||||
3. If an option is declared and a default value has been set, but no value
|
||||
has been set, the returned value is the default value of the option,
|
||||
|
||||
4. If an option is declared, and a value has been set, the returned value is
|
||||
the value of the option.
|
||||
|
||||
But there are special exceptions. We will see later on that an option can be a
|
||||
:term:`mandatory option`. A mandatory option is an option that must have a value
|
||||
defined.
|
||||
|
||||
Searching for an option
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In an application, knowing the path of an option is not always feasible.
|
||||
That's why a tree of options can easily be searched with the :func:`find()` method.
|
||||
|
||||
Let's find an option by it's name
|
||||
|
||||
And let's find first an option by it's name
|
||||
|
||||
The search can be performed in a subtree
|
||||
|
||||
.. code-block:: bash
|
||||
:emphasize-lines: 1, 6, 19
|
||||
|
||||
print(cfg.option.find(name='var1'))
|
||||
# [<tiramisu.api.TiramisuOption object at 0x7f490a530f98>, <tiramisu.api.TiramisuOption object at 0x7f490a530748>]
|
||||
|
||||
# If the option name is unique, the search can be stopped once one matched option
|
||||
# has been found:
|
||||
print(cfg.option.find(name='var1', first=True))
|
||||
# <tiramisu.api.TiramisuOption object at 0x7f6c2beae128>
|
||||
|
||||
# a search object behaves like a cfg object, for example
|
||||
print(cfg.option.find(name='var1', first=True).option.name())
|
||||
# 'var1'
|
||||
print(cfg.option.find(name='var1', first=True).option.doc())
|
||||
|
||||
# a search can be made with various criteria
|
||||
print(cfg.option.find(name='var3', value=undefined))
|
||||
print(cfg.option.find(name='var3', type=StrOption))
|
||||
|
||||
# the find method can be used in subconfigs
|
||||
print(cfg.option('od2').find('var1'))
|
||||
|
||||
:download:`download the config used for the find <src/find.py>`
|
||||
|
||||
The `get` flattening utility
|
||||
-------------------------------------
|
||||
|
||||
In a config or a subconfig, you can print a dict-like representation
|
||||
|
||||
.. code-block:: bash
|
||||
:emphasize-lines: 2
|
||||
|
||||
# get the `od1` option description
|
||||
print(cfg.option('od1').value.get())
|
||||
{'od1.var1': 'éééé', 'od1.var2': 'value'}
|
129
docs/calculation.rst
Normal file
|
@ -0,0 +1,129 @@
|
|||
==================================
|
||||
Calculation
|
||||
==================================
|
||||
|
||||
Calculation is a generic object that allow you to call an external function.
|
||||
|
||||
Simple calculation
|
||||
==================================
|
||||
|
||||
It's structure is the following :
|
||||
|
||||
.. literalinclude:: src/calculation.py
|
||||
:lines: 1-5
|
||||
:linenos:
|
||||
|
||||
Positional and keyword arguments
|
||||
=========================================
|
||||
|
||||
Function's arguments are also specified in this object.
|
||||
Let's see with a positional argument and a keyword argument:
|
||||
|
||||
.. literalinclude:: src/calculation.py
|
||||
:lines: 8-10
|
||||
:linenos:
|
||||
|
||||
The function `a_function_with_parameters` will be call with the positional argument `value1` to `my value 1` and the keyword argument `value2` to `my value 2`.
|
||||
So when this function will be executed, it will return `my value 1 my value 2`.
|
||||
|
||||
Let's see with two positional arguments:
|
||||
|
||||
.. literalinclude:: src/calculation.py
|
||||
:lines: 13-15
|
||||
:linenos:
|
||||
|
||||
As we have several positional arguments, the first Params' argument is a tuple.
|
||||
This example will return strictly same result has previous example.
|
||||
|
||||
Option has an argument
|
||||
=========================================
|
||||
|
||||
In previous examples, we use ParamValue arguments, which could contain random value. But this value is static and cannot be change.
|
||||
|
||||
It could be interesting to use an existant option has an argument:
|
||||
|
||||
.. literalinclude:: src/calculation.py
|
||||
:lines: 18-21
|
||||
:linenos:
|
||||
|
||||
As long as option1 is at its default value, the function will return `1`. If we set option1 to `12`, the function will return `12`.
|
||||
|
||||
Pay attention to the properties when you use an option.
|
||||
This example will raise a ConfigError:
|
||||
|
||||
.. literalinclude:: src/calculation.py
|
||||
:lines: 24-27
|
||||
:linenos:
|
||||
|
||||
It's up to you to define the desired behavior.
|
||||
|
||||
If you want the option to be transitively disabled just set the raisepropertyerror argument to True:
|
||||
|
||||
.. literalinclude:: src/calculation.py
|
||||
:lines: 29-31
|
||||
:linenos:
|
||||
|
||||
If you want to remove option in argument, just set the notraisepropertyerror argument to True:
|
||||
|
||||
.. literalinclude:: src/calculation.py
|
||||
:lines: 33-35
|
||||
:linenos:
|
||||
|
||||
In this case, option1 will not pass to function. You have to set a default value to this argument.
|
||||
So, function will return `None`.
|
||||
|
||||
In these examples, the function only accesses to the value of the option. But no additional information is given.
|
||||
It is possible to add the parameter `todict` to `True` to have the description of the option in addition to its value.
|
||||
|
||||
.. literalinclude:: src/calculation.py
|
||||
:lines: 37-39
|
||||
:linenos:
|
||||
|
||||
This function will return `the option first option has value 1`.
|
||||
|
||||
Multi option has an argument
|
||||
=========================================
|
||||
|
||||
An option could be a multi. Here is an example:
|
||||
|
||||
.. literalinclude:: src/calculation.py
|
||||
:lines: 46-49
|
||||
:linenos:
|
||||
|
||||
In this case the function will return the complete list. So `[1]` in this example.
|
||||
|
||||
Leader or follower option has an argument
|
||||
============================================
|
||||
|
||||
An option could be a leader:
|
||||
|
||||
.. literalinclude:: src/calculation.py
|
||||
:lines: 51-57
|
||||
:linenos:
|
||||
|
||||
If the calculation is used in a standard multi, it will return `[1]`.
|
||||
If the calculation is used in a follower, it will return `1`.
|
||||
|
||||
An option could be a follower:
|
||||
|
||||
.. literalinclude:: src/calculation.py
|
||||
:lines: 59-65
|
||||
:linenos:
|
||||
|
||||
If the calculation is used in a standard multi, it will return `[2]`.
|
||||
If the calculation is used in a follower, it will return `2`.
|
||||
|
||||
If the calculation is used in a follower we can also retrieve the actual follower index:
|
||||
|
||||
.. literalinclude:: src/calculation.py
|
||||
:lines: 67-73
|
||||
:linenos:
|
||||
|
||||
Context has an argument
|
||||
=========================================
|
||||
|
||||
It is possible to recover a copy of the context directly in a function. On the other hand, the use of the context in a function is a slow action which will haunt the performances. Use it only in case of necessity:
|
||||
|
||||
.. literalinclude:: src/calculation.py
|
||||
:lines: 42-44
|
||||
:linenos:
|
718
docs/cmdline_parser.rst
Normal file
|
@ -0,0 +1,718 @@
|
|||
.. .. default-role:: code
|
||||
..
|
||||
.. ==========================
|
||||
.. Tiramisu-cmdline-parser
|
||||
.. ==========================
|
||||
..
|
||||
..
|
||||
.. This tutorial is intended to be a gentle introduction to **Tiramisu
|
||||
.. command-line parser**, a command-line parsing module that comes included with
|
||||
.. the **Tiramisu**'s library.
|
||||
..
|
||||
.. .. note:: There are a lot of other modules that fulfill the same task,
|
||||
.. namely getopt (an equivalent for getopt() from the C language) and
|
||||
.. argparse, from the python standard library.
|
||||
..
|
||||
.. `tiramisu-cmdline-parser` enables us to *validate* the command line,
|
||||
.. wich is a quite different scope -- much more powerfull. It is a
|
||||
.. superset of the argparse_ module
|
||||
..
|
||||
.. .. _argparse: https://docs.python.org/3/howto/argparse.html
|
||||
..
|
||||
.. What is Tiramisu-cmdline-parser ?
|
||||
.. ==================================
|
||||
..
|
||||
.. Tiramisu-cmdline-parser is a free project that turns Tiramisu's Config into a command line interface.
|
||||
..
|
||||
.. It automatically generates arguments, help and usage messages. Tiramisu (or
|
||||
.. Tiramisu-API) validates all arguments provided by the command line's user.
|
||||
..
|
||||
.. Tiramisu-cmdline-parser uses the well known argparse_ module and adds
|
||||
.. functionnalities upon it.
|
||||
..
|
||||
..
|
||||
.. Installation
|
||||
.. ==============
|
||||
..
|
||||
.. The best way is to use the python pip_ installer
|
||||
..
|
||||
.. .. _pip: https://pip.pypa.io/en/stable/installing/
|
||||
..
|
||||
.. And then type:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. pip install tiramisu-cmdline-parser
|
||||
..
|
||||
.. Build a Tiramisu-cmdline-parser
|
||||
.. =================================
|
||||
..
|
||||
.. Let’s show the sort of functionality that we are going to explore in this
|
||||
.. introductory tutorial.
|
||||
..
|
||||
.. We are going to start with a simple example, like making a proxy's
|
||||
.. configuration script.
|
||||
..
|
||||
.. First we are going to build the corresponding `Tiramisu` config object:
|
||||
..
|
||||
.. .. literalinclude:: src/proxy.py
|
||||
.. :lines: 1-44
|
||||
.. :linenos:
|
||||
.. :name: Proxy1
|
||||
..
|
||||
.. Then we invopque the command line parsing library by creating a commandline
|
||||
.. parser, and we give the configuration's object to it:
|
||||
..
|
||||
.. .. literalinclude:: src/proxy.py
|
||||
.. :lines: 46-48
|
||||
.. :linenos:
|
||||
.. :name: Proxy2
|
||||
..
|
||||
.. Finally pretty printing the configuration:
|
||||
..
|
||||
.. .. literalinclude:: src/proxy.py
|
||||
.. :lines: 50-51
|
||||
.. :linenos:
|
||||
.. :name: Proxy3
|
||||
..
|
||||
.. Let's display the help:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py -h
|
||||
.. usage: proxy.py [-h] [--dns_over_https] [--no-dns_over_https]
|
||||
.. {No proxy,Manual proxy configuration,Automatic proxy
|
||||
.. configuration URL}
|
||||
..
|
||||
.. positional arguments:
|
||||
.. {No proxy,Manual proxy configuration,Automatic proxy configuration URL}
|
||||
.. Proxy's config mode
|
||||
..
|
||||
.. optional arguments:
|
||||
.. -h, --help show this help message and exit
|
||||
.. --dns_over_https Enable DNS over HTTPS
|
||||
.. --no-dns_over_https
|
||||
..
|
||||
.. Positional argument
|
||||
.. ======================
|
||||
..
|
||||
.. First of all, we have to set the positional argument :option:`proxy_mode`.
|
||||
..
|
||||
.. .. option:: proxy_mode
|
||||
..
|
||||
.. As it's a `ChoiceOption`, you only have three choices:
|
||||
..
|
||||
.. - No proxy
|
||||
.. - Manual proxy configuration
|
||||
.. - Automatic proxy configuration URL
|
||||
..
|
||||
.. Set proxy_mode to `No proxy`:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "No proxy"
|
||||
.. {'dns_over_https': False,
|
||||
.. 'proxy_mode': 'No proxy'}
|
||||
..
|
||||
.. Requirements
|
||||
.. ================
|
||||
..
|
||||
.. Disabled options are not visible as arguments in the command line.
|
||||
.. Those parameters appears or disappears following the context:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "No proxy" -h
|
||||
.. usage: proxy.py "No proxy" [-h] [--dns_over_https] [--no-dns_over_https]
|
||||
.. {No proxy,Manual proxy configuration,Automatic
|
||||
.. proxy configuration URL}
|
||||
..
|
||||
.. positional arguments:
|
||||
.. {No proxy,Manual proxy configuration,Automatic proxy configuration URL}
|
||||
.. Proxy's config mode
|
||||
..
|
||||
.. optional arguments:
|
||||
.. -h, --help show this help message and exit
|
||||
.. --dns_over_https Enable DNS over HTTPS
|
||||
.. --no-dns_over_https
|
||||
..
|
||||
.. If proxy_mode is set to "Automatic proxy configuration URL", some new options are visible:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "Automatic proxy configuration URL" -h
|
||||
.. usage: proxy.py "Automatic proxy configuration URL" [-h] -i AUTO_CONFIG_URL
|
||||
.. [--no_proxy.no_proxy_network.no_proxy_network [NO_PROXY_NETWORK [NO_PROXY_NETWORK ...]]]
|
||||
.. [--no_proxy.no_proxy_network.pop-no_proxy_network INDEX]
|
||||
.. --no_proxy.no_proxy_network.no_proxy_netmask
|
||||
.. INDEX NO_PROXY_NETMASK
|
||||
.. [--no_proxy.no_proxy_domain [NO_PROXY_DOMAIN [NO_PROXY_DOMAIN ...]]]
|
||||
.. [--dns_over_https]
|
||||
.. [--no-dns_over_https]
|
||||
.. {No proxy,Manual proxy
|
||||
.. configuration,Automatic
|
||||
.. proxy configuration URL}
|
||||
..
|
||||
.. positional arguments:
|
||||
.. {No proxy,Manual proxy configuration,Automatic proxy configuration URL}
|
||||
.. Proxy's config mode
|
||||
..
|
||||
.. optional arguments:
|
||||
.. -h, --help show this help message and exit
|
||||
.. --dns_over_https Enable DNS over HTTPS
|
||||
.. --no-dns_over_https
|
||||
..
|
||||
.. configuration.automatic_proxy:
|
||||
.. Automatic proxy setting
|
||||
..
|
||||
.. -i AUTO_CONFIG_URL, --configuration.automatic_proxy.auto_config_url AUTO_CONFIG_URL
|
||||
.. Proxy's auto config URL
|
||||
..
|
||||
.. no_proxy:
|
||||
.. Disabled proxy
|
||||
..
|
||||
.. --no_proxy.no_proxy_domain [NO_PROXY_DOMAIN [NO_PROXY_DOMAIN ...]]
|
||||
.. Domain names for which proxy will be desactivated
|
||||
..
|
||||
.. no_proxy.no_proxy_network:
|
||||
.. Network for which proxy will be desactivated
|
||||
..
|
||||
.. --no_proxy.no_proxy_network.no_proxy_network [NO_PROXY_NETWORK [NO_PROXY_NETWORK ...]]
|
||||
.. Network addresses
|
||||
.. --no_proxy.no_proxy_network.pop-no_proxy_network INDEX
|
||||
.. --no_proxy.no_proxy_network.no_proxy_netmask INDEX NO_PROXY_NETMASK
|
||||
.. Netmask addresses
|
||||
..
|
||||
.. Arguments
|
||||
.. ===========
|
||||
..
|
||||
.. Each option creates an argument. To change the value of this option, just launch the application with the appropriate argument:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "Manual proxy configuration" \
|
||||
.. --configuration.manual_proxy.http_ip_address 192.168.1.1
|
||||
.. {'configuration.manual_proxy.http_ip_address': '192.168.1.1',
|
||||
.. 'configuration.manual_proxy.http_port': '8080',
|
||||
.. 'configuration.manual_proxy.i': '192.168.1.1',
|
||||
.. 'configuration.manual_proxy.p': '8080',
|
||||
.. 'dns_over_https': False,
|
||||
.. 'no_proxy.no_proxy_domain': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_netmask': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_network': [],
|
||||
.. 'proxy_mode': 'Manual proxy configuration'}
|
||||
..
|
||||
.. Fullpath argument or named argument
|
||||
.. =====================================
|
||||
..
|
||||
.. By default, arguments are build with fullpath of option.
|
||||
.. The `option http_ip_address` is in `manual_proxy` optiondescription, which is also in configuration optiondescription.
|
||||
.. So the argument is :option:`--configuration.manual_proxy.http_ip_address`:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "Manual proxy configuration" \
|
||||
.. --configuration.manual_proxy.http_ip_address 192.168.1.1
|
||||
.. {'configuration.manual_proxy.http_ip_address': '192.168.1.1',
|
||||
.. 'configuration.manual_proxy.http_port': '8080',
|
||||
.. 'configuration.manual_proxy.i': '192.168.1.1',
|
||||
.. 'configuration.manual_proxy.p': '8080',
|
||||
.. 'dns_over_https': False,
|
||||
.. 'no_proxy.no_proxy_domain': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_netmask': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_network': [],
|
||||
.. 'proxy_mode': 'Manual proxy configuration'}
|
||||
..
|
||||
.. If we set fullpath to `False`:
|
||||
..
|
||||
.. .. code-block:: python
|
||||
..
|
||||
.. parser = TiramisuCmdlineParser(proxy_config, fullpath=False)
|
||||
..
|
||||
.. Arguments are build with the name of the option.
|
||||
.. The option :option:`http_ip_address` is now :option`--http_ip_address`:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "Manual proxy configuration" \
|
||||
.. --http_ip_address 192.168.1.1
|
||||
.. {'configuration.manual_proxy.http_ip_address': '192.168.1.1',
|
||||
.. 'configuration.manual_proxy.http_port': '8080',
|
||||
.. 'configuration.manual_proxy.i': '192.168.1.1',
|
||||
.. 'configuration.manual_proxy.p': '8080',
|
||||
.. 'dns_over_https': False,
|
||||
.. 'no_proxy.no_proxy_domain': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_netmask': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_network': [],
|
||||
.. 'proxy_mode': 'Manual proxy configuration'}
|
||||
..
|
||||
.. Short argument
|
||||
.. ===============
|
||||
..
|
||||
.. To have short argument, you just have to make `SymLinkOption` to this option:
|
||||
..
|
||||
.. .. literalinclude:: src/proxy.py
|
||||
.. :lines: 10-11
|
||||
.. :linenos:
|
||||
.. :name: Proxy4
|
||||
..
|
||||
.. Now argument `-i` or `--configuration.manual_proxy.http_ip_address` can be used alternatively:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "Manual proxy configuration" \
|
||||
.. --configuration.manual_proxy.http_ip_address 192.168.1.1
|
||||
.. {'configuration.manual_proxy.http_ip_address': '192.168.1.1',
|
||||
.. 'configuration.manual_proxy.http_port': '8080',
|
||||
.. 'configuration.manual_proxy.i': '192.168.1.1',
|
||||
.. 'configuration.manual_proxy.p': '8080',
|
||||
.. 'dns_over_https': False,
|
||||
.. 'no_proxy.no_proxy_domain': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_netmask': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_network': [],
|
||||
.. 'proxy_mode': 'Manual proxy configuration'}
|
||||
..
|
||||
..
|
||||
.. $ python3 src/proxy.py "Manual proxy configuration" \
|
||||
.. -i 192.168.1.1
|
||||
.. {'configuration.manual_proxy.http_ip_address': '192.168.1.1',
|
||||
.. 'configuration.manual_proxy.http_port': '8080',
|
||||
.. 'configuration.manual_proxy.i': '192.168.1.1',
|
||||
.. 'configuration.manual_proxy.p': '8080',
|
||||
.. 'dns_over_https': False,
|
||||
.. 'no_proxy.no_proxy_domain': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_netmask': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_network': [],
|
||||
.. 'proxy_mode': 'Manual proxy configuration'}
|
||||
..
|
||||
.. Be carefull, short argument have to be uniqe in the whole configuration.
|
||||
..
|
||||
.. Here `-i` argument is define a second time in same Config:
|
||||
..
|
||||
.. .. literalinclude:: src/proxy.py
|
||||
.. :lines: 17-18
|
||||
.. :linenos:
|
||||
.. :name: Proxy5
|
||||
..
|
||||
.. But `http_ip_address` and `auto_config_url` are not accessible together:
|
||||
..
|
||||
.. - `http_ip_address` is visible only if `proxy_mode` is "Manual proxy configuration"
|
||||
.. - `auto_config_url` is only visible when `proxy_mode` is "Automatic proxy configuration URL"
|
||||
..
|
||||
.. Boolean argument
|
||||
.. ===================
|
||||
..
|
||||
.. Boolean option creates two arguments:
|
||||
..
|
||||
.. - --<boolean_name>: it activates (set to True) the option
|
||||
.. - --no-<boolean_name>: it deactivates (set to False) the option
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "No proxy" \
|
||||
.. --dns_over_https
|
||||
.. {'dns_over_https': True,
|
||||
.. 'proxy_mode': 'No proxy'}
|
||||
..
|
||||
.. $ python3 src/proxy.py "No proxy" \
|
||||
.. --no-dns_over_https
|
||||
.. {'dns_over_https': False,
|
||||
.. 'proxy_mode': 'No proxy'}
|
||||
..
|
||||
.. Multi
|
||||
.. =========
|
||||
..
|
||||
.. Some values are multi. So we can set several value for this option.
|
||||
..
|
||||
.. For example, we can set serveral domain (cadoles.com and gnu.org) to "Domain names for which proxy will be desactivated" option:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "Automatic proxy configuration URL" \
|
||||
.. --configuration.automatic_proxy.auto_config_url http://proxy.cadoles.com/proxy.pac \
|
||||
.. --no_proxy.no_proxy_domain cadoles.com gnu.org
|
||||
.. {'configuration.automatic_proxy.auto_config_url': 'http://proxy.cadoles.com/proxy.pac',
|
||||
.. 'configuration.automatic_proxy.i': 'http://proxy.cadoles.com/proxy.pac',
|
||||
.. 'dns_over_https': False,
|
||||
.. 'no_proxy.no_proxy_domain': ['cadoles.com', 'gnu.org'],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_netmask': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_network': [],
|
||||
.. 'proxy_mode': 'Automatic proxy configuration URL'}
|
||||
..
|
||||
.. Leadership
|
||||
.. ============
|
||||
..
|
||||
.. Leadership option are also supported. The leader option is a standard multi option.
|
||||
.. But follower option are not view as a multi option. Follower value are separate and we need to set index to set a follower option.
|
||||
..
|
||||
.. If we want to had two "Network for which proxy will be desactivated":
|
||||
..
|
||||
.. - 192.168.1.1/255.255.255.255
|
||||
.. - 192.168.0.0/255.255.255.0
|
||||
..
|
||||
.. We have to do:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "Automatic proxy configuration URL" \
|
||||
.. --configuration.automatic_proxy.auto_config_url http://proxy.cadoles.com/proxy.pac \
|
||||
.. --no_proxy.no_proxy_network.no_proxy_network 192.168.1.1 192.168.0.0 \
|
||||
.. --no_proxy.no_proxy_network.no_proxy_netmask 0 255.255.255.255 \
|
||||
.. --no_proxy.no_proxy_network.no_proxy_netmask 1 255.255.255.0
|
||||
.. {'configuration.automatic_proxy.auto_config_url': 'http://proxy.cadoles.com/proxy.pac',
|
||||
.. 'configuration.automatic_proxy.i': 'http://proxy.cadoles.com/proxy.pac',
|
||||
.. 'dns_over_https': False,
|
||||
.. 'no_proxy.no_proxy_domain': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_netmask': ['255.255.255.255',
|
||||
.. '255.255.255.0'],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_network': ['192.168.1.1', '192.168.0.0'],
|
||||
.. 'proxy_mode': 'Automatic proxy configuration URL'}
|
||||
..
|
||||
.. We cannot reduce leader lenght:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "Automatic proxy configuration URL" \
|
||||
.. --configuration.automatic_proxy.auto_config_url http://proxy.cadoles.com/proxy.pac \
|
||||
.. --no_proxy.no_proxy_network.no_proxy_network 192.168.1.1 192.168.0.0 \
|
||||
.. --no_proxy.no_proxy_network.no_proxy_netmask 0 255.255.255.255 \
|
||||
.. --no_proxy.no_proxy_network.no_proxy_netmask 1 255.255.255.0 \
|
||||
.. --no_proxy.no_proxy_network.no_proxy_network 192.168.1.1
|
||||
.. usage: proxy.py -i "http://proxy.cadoles.com/proxy.pac" --no_proxy.no_proxy_network.no_proxy_network "192.168.1.1" "192.168.0.0" "Automatic proxy configuration URL"
|
||||
.. [-h] -i AUTO_CONFIG_URL
|
||||
.. [--no_proxy.no_proxy_network.no_proxy_network [NO_PROXY_NETWORK [NO_PROXY_NETWORK ...]]]
|
||||
.. [--no_proxy.no_proxy_network.pop-no_proxy_network INDEX]
|
||||
.. --no_proxy.no_proxy_network.no_proxy_netmask INDEX NO_PROXY_NETMASK
|
||||
.. [--no_proxy.no_proxy_domain [NO_PROXY_DOMAIN [NO_PROXY_DOMAIN ...]]]
|
||||
.. [--dns_over_https] [--no-dns_over_https]
|
||||
.. {No proxy,Manual proxy configuration,Automatic proxy configuration URL}
|
||||
.. proxy.py: error: cannot reduce length of the leader "--no_proxy.no_proxy_network.no_proxy_network"
|
||||
..
|
||||
.. So an argument --pop-<leader> is automatically created. You need to specified index as parameter:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "Automatic proxy configuration URL" \
|
||||
.. --configuration.automatic_proxy.auto_config_url http://proxy.cadoles.com/proxy.pac \
|
||||
.. --no_proxy.no_proxy_network.no_proxy_network 192.168.1.1 192.168.0.0 \
|
||||
.. --no_proxy.no_proxy_network.no_proxy_netmask 0 255.255.255.255 \
|
||||
.. --no_proxy.no_proxy_network.pop-no_proxy_network 1
|
||||
.. {'configuration.automatic_proxy.auto_config_url': 'http://proxy.cadoles.com/proxy.pac',
|
||||
.. 'configuration.automatic_proxy.i': 'http://proxy.cadoles.com/proxy.pac',
|
||||
.. 'dns_over_https': False,
|
||||
.. 'no_proxy.no_proxy_domain': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_netmask': ['255.255.255.255'],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_network': ['192.168.1.1'],
|
||||
.. 'proxy_mode': 'Automatic proxy configuration URL'}
|
||||
..
|
||||
.. Validation
|
||||
.. ===============
|
||||
..
|
||||
.. All arguments are validated successively by argparser and Tiramisu:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "Automatic proxy configuration URL" \
|
||||
.. --configuration.automatic_proxy.auto_config_url cadoles.com
|
||||
.. usage: proxy.py "Automatic proxy configuration URL" [-h] -i AUTO_CONFIG_URL
|
||||
.. [--no_proxy.no_proxy_network.no_proxy_network [NO_PROXY_NETWORK [NO_PROXY_NETWORK ...]]]
|
||||
.. [--no_proxy.no_proxy_network.pop-no_proxy_network INDEX]
|
||||
.. --no_proxy.no_proxy_network.no_proxy_netmask
|
||||
.. INDEX NO_PROXY_NETMASK
|
||||
.. [--no_proxy.no_proxy_domain [NO_PROXY_DOMAIN [NO_PROXY_DOMAIN ...]]]
|
||||
.. [--dns_over_https]
|
||||
.. [--no-dns_over_https]
|
||||
.. {No proxy,Manual proxy
|
||||
.. configuration,Automatic
|
||||
.. proxy configuration URL}
|
||||
.. proxy.py: error: "cadoles.com" is an invalid URL for "Proxy’s auto config URL", must start with http:// or https://
|
||||
..
|
||||
.. In error message, we have the option description ("Proxy's auto config URL") by default.
|
||||
..
|
||||
.. That why we redefined display_name function:
|
||||
..
|
||||
.. .. literalinclude:: src/proxy.py
|
||||
.. :lines: 40-43
|
||||
.. :linenos:
|
||||
.. :name: Proxy6
|
||||
..
|
||||
.. Now we have --<path> as description:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "Automatic proxy configuration URL" \
|
||||
.. --configuration.automatic_proxy.auto_config_url cadoles.com
|
||||
.. usage: proxy.py "Automatic proxy configuration URL" [-h] -i AUTO_CONFIG_URL
|
||||
.. [--no_proxy.no_proxy_network.no_proxy_network [NO_PROXY_NETWORK [NO_PROXY_NETWORK ...]]]
|
||||
.. [--no_proxy.no_proxy_network.pop-no_proxy_network INDEX]
|
||||
.. --no_proxy.no_proxy_network.no_proxy_netmask
|
||||
.. INDEX NO_PROXY_NETMASK
|
||||
.. [--no_proxy.no_proxy_domain [NO_PROXY_DOMAIN [NO_PROXY_DOMAIN ...]]]
|
||||
.. [--dns_over_https]
|
||||
.. [--no-dns_over_https]
|
||||
.. {No proxy,Manual proxy
|
||||
.. configuration,Automatic
|
||||
.. proxy configuration URL}
|
||||
.. proxy.py: error: "cadoles.com" is an invalid URL for "--configuration.automatic_proxy.auto_config_url", must start with http:// or https://
|
||||
..
|
||||
.. Mandatory
|
||||
.. =============
|
||||
..
|
||||
.. Obviously the mandatory options are checked.
|
||||
..
|
||||
.. The positional argument is mandatory, so if we don't set it, an error occured:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py
|
||||
.. usage: proxy.py [-h] [--dns_over_https] [--no-dns_over_https]
|
||||
.. {No proxy,Manual proxy configuration,Automatic proxy
|
||||
.. configuration URL}
|
||||
.. proxy.py: error: the following arguments are required: proxy_mode
|
||||
..
|
||||
.. Others arguments are also check:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "Automatic proxy configuration URL"
|
||||
.. usage: proxy.py "Automatic proxy configuration URL" [-h] -i AUTO_CONFIG_URL
|
||||
.. [--no_proxy.no_proxy_network.no_proxy_network [NO_PROXY_NETWORK [NO_PROXY_NETWORK ...]]]
|
||||
.. [--no_proxy.no_proxy_network.pop-no_proxy_network INDEX]
|
||||
.. --no_proxy.no_proxy_network.no_proxy_netmask
|
||||
.. INDEX NO_PROXY_NETMASK
|
||||
.. [--no_proxy.no_proxy_domain [NO_PROXY_DOMAIN [NO_PROXY_DOMAIN ...]]]
|
||||
.. [--dns_over_https]
|
||||
.. [--no-dns_over_https]
|
||||
.. {No proxy,Manual proxy
|
||||
.. configuration,Automatic
|
||||
.. proxy configuration URL}
|
||||
.. proxy.py: error: the following arguments are required: --configuration.automatic_proxy.auto_config_url
|
||||
..
|
||||
.. Persistence configuration and mandatories validation
|
||||
.. ======================================================
|
||||
..
|
||||
.. First of all, activate persistence configuration and remove mandatory validation:
|
||||
..
|
||||
.. .. literalinclude:: src/proxy_persistent.py
|
||||
.. :lines: 43-46
|
||||
.. :linenos:
|
||||
.. :name: Proxy7
|
||||
..
|
||||
.. We can disabled mandatory validation in parse_args function.
|
||||
..
|
||||
.. .. literalinclude:: src/proxy_persistent.py
|
||||
.. :lines: 51
|
||||
.. :linenos:
|
||||
.. :name: Proxy8
|
||||
..
|
||||
.. In this case, we can store incomplete value:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy_persistent.py 'Manual proxy configuration'
|
||||
.. {'configuration.manual_proxy.http_ip_address': None,
|
||||
.. 'configuration.manual_proxy.http_port': '8080',
|
||||
.. 'configuration.manual_proxy.i': None,
|
||||
.. 'configuration.manual_proxy.p': '8080',
|
||||
.. 'dns_over_https': False,
|
||||
.. 'no_proxy.no_proxy_domain': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_netmask': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_network': [],
|
||||
.. 'proxy_mode': 'Manual proxy configuration'}
|
||||
..
|
||||
.. We can complete configuration after:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy_persistent.py -i 192.168.1.1
|
||||
.. {'configuration.manual_proxy.http_ip_address': '192.168.1.1',
|
||||
.. 'configuration.manual_proxy.http_port': '8080',
|
||||
.. 'configuration.manual_proxy.i': '192.168.1.1',
|
||||
.. 'configuration.manual_proxy.p': '8080',
|
||||
.. 'dns_over_https': False,
|
||||
.. 'no_proxy.no_proxy_domain': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_netmask': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_network': [],
|
||||
.. 'proxy_mode': 'Manual proxy configuration'}
|
||||
..
|
||||
.. When configuration is already set, help command, display already set options is usage ligne.
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy_persistent.py -h
|
||||
.. usage: proxy_persistent.py -i "192.168.1.1" "Manual proxy configuration"
|
||||
.. [..]
|
||||
..
|
||||
.. Description and epilog
|
||||
.. ============================
|
||||
..
|
||||
.. As argparser, description and epilog message can be added to the generated help:
|
||||
..
|
||||
.. .. code-block:: python
|
||||
..
|
||||
.. parser = TiramisuCmdlineParser(proxy_config, description='New description!', epilog='New epilog!')
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py -h
|
||||
.. usage: proxy.py [-h] [--dns_over_https] [--no-dns_over_https]
|
||||
.. {No proxy,Manual proxy configuration,Automatic proxy
|
||||
.. configuration URL}
|
||||
..
|
||||
.. New description!
|
||||
..
|
||||
.. positional arguments:
|
||||
.. {No proxy,Manual proxy configuration,Automatic proxy configuration URL}
|
||||
.. Proxy's config mode
|
||||
..
|
||||
.. optional arguments:
|
||||
.. -h, --help show this help message and exit
|
||||
.. --dns_over_https Enable DNS over HTTPS
|
||||
.. --no-dns_over_https
|
||||
..
|
||||
.. New epilog!
|
||||
..
|
||||
..
|
||||
.. By default, TiramisuCmdlineParser objects line-wrap the description and epilog texts in command-line help messages.
|
||||
..
|
||||
.. If there are line breaks in description or epilog, it automatically replace by a space. You need to change formatter class:
|
||||
..
|
||||
.. .. code-block:: python
|
||||
..
|
||||
.. from argparse import RawDescriptionHelpFormatter
|
||||
.. parser = TiramisuCmdlineParser(proxy_config, description='New description!\nLine breaks', epilog='New epilog!\nLine breaks', formatter_class=RawDescriptionHelpFormatter)
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py -h
|
||||
.. usage: proxy.py [-h] [--dns_over_https] [--no-dns_over_https]
|
||||
.. {No proxy,Manual proxy configuration,Automatic proxy
|
||||
.. configuration URL}
|
||||
..
|
||||
.. New description!
|
||||
.. Line breaks
|
||||
..
|
||||
.. positional arguments:
|
||||
.. {No proxy,Manual proxy configuration,Automatic proxy configuration URL}
|
||||
.. Proxy's config mode
|
||||
..
|
||||
.. optional arguments:
|
||||
.. -h, --help show this help message and exit
|
||||
.. --dns_over_https Enable DNS over HTTPS
|
||||
.. --no-dns_over_https
|
||||
..
|
||||
.. New epilog!
|
||||
.. Line breaks
|
||||
..
|
||||
.. Hide empty optiondescription
|
||||
.. ===============================
|
||||
..
|
||||
.. An empty optiondescription, is an optiondescription without any option (could have others optiondescriptions).
|
||||
..
|
||||
.. For example, configuration is an empty optiondescription:
|
||||
..
|
||||
.. .. literalinclude:: src/proxy.py
|
||||
.. :lines: 23-24
|
||||
.. :linenos:
|
||||
.. :name: Proxy9
|
||||
..
|
||||
.. This optiondescription doesn't appears in help:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "No proxy" -h
|
||||
.. usage: proxy.py "No proxy" [-h] [--dns_over_https] [--no-dns_over_https]
|
||||
.. {No proxy,Manual proxy configuration,Automatic
|
||||
.. proxy configuration URL}
|
||||
..
|
||||
.. positional arguments:
|
||||
.. {No proxy,Manual proxy configuration,Automatic proxy configuration URL}
|
||||
.. Proxy's config mode
|
||||
..
|
||||
.. optional arguments:
|
||||
.. -h, --help show this help message and exit
|
||||
.. --dns_over_https Enable DNS over HTTPS
|
||||
.. --no-dns_over_https
|
||||
..
|
||||
..
|
||||
..
|
||||
.. This behavior is, in fact, due to two conditions:
|
||||
..
|
||||
.. - there is no option
|
||||
.. - there is no description (None)
|
||||
..
|
||||
.. If we add description:
|
||||
..
|
||||
.. .. code-block:: python
|
||||
..
|
||||
.. configuration = OptionDescription('configuration', 'Configuration',
|
||||
.. [manual_proxy, automatic_proxy])
|
||||
..
|
||||
.. This optiondescription is specified in help:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "No proxy" -h
|
||||
.. usage: proxy.py "No proxy" [-h] [--dns_over_https] [--no-dns_over_https]
|
||||
.. {No proxy,Manual proxy configuration,Automatic
|
||||
.. proxy configuration URL}
|
||||
..
|
||||
.. positional arguments:
|
||||
.. {No proxy,Manual proxy configuration,Automatic proxy configuration URL}
|
||||
.. Proxy's config mode
|
||||
..
|
||||
.. optional arguments:
|
||||
.. -h, --help show this help message and exit
|
||||
.. --dns_over_https Enable DNS over HTTPS
|
||||
.. --no-dns_over_https
|
||||
..
|
||||
.. configuration:
|
||||
.. Configuration
|
||||
..
|
||||
.. If you don't want empty optiondescription even if there is a description, you could add remove_empty_od to True in parse_args function:
|
||||
..
|
||||
.. .. code-block:: python
|
||||
..
|
||||
.. parser = TiramisuCmdlineParser(proxy_config, remove_empty_od=True)
|
||||
..
|
||||
.. SubConfig
|
||||
.. ================
|
||||
..
|
||||
.. Entire Config is transformed into an argument by default.
|
||||
..
|
||||
.. It could be interesting to display only 'configuration' OptionDescription.
|
||||
..
|
||||
.. To do this, we have to define default all mandatories options outside this scope:
|
||||
..
|
||||
.. .. code-block:: python
|
||||
..
|
||||
.. proxy_mode = ChoiceOption('proxy_mode', 'Proxy\'s config mode', ('No proxy',
|
||||
.. 'Manual proxy configuration',
|
||||
.. 'Automatic proxy configuration URL'),
|
||||
.. default='Manual proxy configuration',
|
||||
.. properties=('positional', 'mandatory'))
|
||||
..
|
||||
.. Finally specified the root argument to `TiramisuCmdlineParser`:
|
||||
..
|
||||
.. .. code-block:: python
|
||||
..
|
||||
.. parser = TiramisuCmdlineParser(proxy_config, root='configuration')
|
||||
..
|
||||
.. Now, only sub option of configuration is proposed:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py -h
|
||||
.. usage: proxy.py [-h] -i HTTP_IP_ADDRESS -p [HTTP_PORT]
|
||||
..
|
||||
.. optional arguments:
|
||||
.. -h, --help show this help message and exit
|
||||
..
|
||||
.. configuration.manual_proxy:
|
||||
.. Manual proxy settings
|
||||
..
|
||||
.. -i HTTP_IP_ADDRESS, --configuration.manual_proxy.http_ip_address HTTP_IP_ADDRESS
|
||||
.. Proxy's HTTP IP
|
||||
.. -p [HTTP_PORT], --configuration.manual_proxy.http_port [HTTP_PORT]
|
||||
.. Proxy's HTTP Port
|
||||
..
|
149
docs/conf.py
Normal file
|
@ -0,0 +1,149 @@
|
|||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file does only contain a selection of the most common options. For a
|
||||
# full list see the documentation:
|
||||
# http://www.sphinx-doc.org/en/master/config
|
||||
|
||||
# -- Path setup --------------------------------------------------------------
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
# import os
|
||||
# import sys
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'Tiramisu'
|
||||
copyright = '2011-2023, Silique'
|
||||
author = 'egarette'
|
||||
|
||||
# The short X.Y version
|
||||
version = ''
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = '4.0'
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#
|
||||
# needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
|
||||
extensions = [
|
||||
'sphinx.ext.extlinks', 'sphinx_lesson',
|
||||
#'myst_parser', 'sphinx.ext.extlinks'
|
||||
]
|
||||
#
|
||||
#myst_enable_extensions = [
|
||||
# "amsmath",
|
||||
# "attrs_inline",
|
||||
# "colon_fence",
|
||||
# "deflist",
|
||||
# "dollarmath",
|
||||
# "fieldlist",
|
||||
# "html_admonition",
|
||||
# "html_image",
|
||||
## "linkify",
|
||||
# "replacements",
|
||||
# "smartquotes",
|
||||
# "strikethrough",
|
||||
# "substitution",
|
||||
# "tasklist",
|
||||
#]
|
||||
|
||||
|
||||
# **extlinks** 'sphinx.ext.extlinks',
|
||||
# enables syntax like :proxy:`my source <hello>` in the src files
|
||||
extlinks = {'proxy': ('/proxy/%s.html',
|
||||
'external link: ')}
|
||||
|
||||
default_role = "code"
|
||||
|
||||
html_theme = "sphinx_rtd_theme"
|
||||
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
html_short_title = "Tiramisu"
|
||||
html_title = "Tiramisu documenation"
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
html_show_sourcelink = False
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
html_show_sphinx = False
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
html_show_copyright = True
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
#
|
||||
#source_suffix = ['.rst', '.md']
|
||||
source_suffix = '.rst'
|
||||
#source_suffix = {
|
||||
# '.rst': 'restructuredtext',
|
||||
# '.txt': 'restructuredtext',
|
||||
# '.md': 'markdown',
|
||||
#}
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = 'en'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path.
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = None
|
||||
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
#html_theme = 'alabaster'
|
||||
# **themes**
|
||||
#html_theme = 'bizstyle'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#
|
||||
# html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# Custom sidebar templates, must be a dictionary that maps document names
|
||||
# to template names.
|
||||
#
|
||||
# The default sidebars (for documents that don't match any pattern) are
|
||||
# defined by theme itself. Builtin themes are using these templates by
|
||||
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
|
||||
# 'searchbox.html']``.
|
||||
#
|
||||
# html_sidebars = {}
|
||||
|
||||
def setup(app):
|
||||
app.add_css_file('css/custom.css')
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
138
docs/config.rst
Normal file
|
@ -0,0 +1,138 @@
|
|||
The :class:`Config`
|
||||
====================
|
||||
|
||||
Tiramisu is made of almost three main classes/concepts :
|
||||
|
||||
- the :class:`Option` stands for the option types
|
||||
- the :class:`OptionDescription` is the schema, the option's structure
|
||||
- the :class:`Config` which is the whole configuration entry point
|
||||
|
||||
.. image:: config.png
|
||||
|
||||
The handling of options
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The handling of options is split into two parts: the description of
|
||||
which options are available, what their possible values and defaults are
|
||||
and how they are organized into a tree. A specific choice of options is
|
||||
bundled into a configuration object which has a reference to its option
|
||||
description (and therefore makes sure that the configuration values
|
||||
adhere to the option description).
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
option
|
||||
options
|
||||
symlinkoption
|
||||
own_option
|
||||
|
||||
Option description are nested Options
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The :class:`Option` (in this case the :class:`BoolOption`),
|
||||
are organized into a tree into nested
|
||||
:class:`~tiramisu.option.OptionDescription` objects.
|
||||
|
||||
Every option has a name, as does every option group.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
optiondescription
|
||||
dynoptiondescription
|
||||
leadership
|
||||
|
||||
|
||||
Config
|
||||
~~~~~~
|
||||
|
||||
Let's perform a *Getting started* code review :
|
||||
|
||||
.. literalinclude:: src/getting_started.py
|
||||
:lines: 1-12
|
||||
:linenos:
|
||||
:name: GettingStarted
|
||||
|
||||
Let's review the code. First, line 7, we create an :class:`OptionDescription` named `optgroup`.
|
||||
|
||||
.. literalinclude:: src/getting_started.py
|
||||
:lines: 4, 6-7
|
||||
:emphasize-lines: 3
|
||||
|
||||
Option objects can be created in different ways, here we create a
|
||||
:class:`BoolOption`
|
||||
|
||||
.. literalinclude:: src/getting_started.py
|
||||
:lines: 4, 8-9
|
||||
:emphasize-lines: 3
|
||||
|
||||
Then, line 12, we make a :class:`Config` with the :class:`OptionDescription` we
|
||||
built :
|
||||
|
||||
.. literalinclude:: src/getting_started.py
|
||||
:lines: 3, 12
|
||||
:emphasize-lines: 2
|
||||
|
||||
Here is how to print our :class:`Config` details:
|
||||
|
||||
.. literalinclude:: src/getting_started.py
|
||||
:lines: 15
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
Root config object that enables us to handle the configuration options
|
||||
|
||||
Settings:
|
||||
forcepermissive Access to option without verifying permissive properties
|
||||
unrestraint Access to option without property restriction
|
||||
nowarnings Do not warnings during validation
|
||||
|
||||
Commands:
|
||||
cache Manage config cache
|
||||
config Actions to Config
|
||||
information Manage config informations
|
||||
option Select an option
|
||||
owner Global owner
|
||||
permissive Manage config permissives
|
||||
property Manage config properties
|
||||
value Manage config value
|
||||
|
||||
Then let's print our :class:`Option` details.
|
||||
|
||||
.. literalinclude:: src/getting_started.py
|
||||
:lines: 17
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
Select an option
|
||||
|
||||
Call: Select an option by path
|
||||
|
||||
Commands:
|
||||
dict Convert config and option to tiramisu format
|
||||
find Find an or a list of options
|
||||
list List options (by default list only option)
|
||||
name get the name
|
||||
updates Updates value with tiramisu format
|
||||
|
||||
Finaly, let's print the :class:`Config`.
|
||||
|
||||
.. literalinclude:: src/getting_started.py
|
||||
:lines: 19
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
<tiramisu.api.Config object at 0x7f3ee6204278>
|
||||
|
||||
:download:`download the getting started code <src/getting_started.py>`
|
||||
|
||||
Go futher with `Option` and `Config`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
property
|
||||
validator
|
||||
calculation
|
39
docs/custom/static/custom.css
Normal file
|
@ -0,0 +1,39 @@
|
|||
@import url("bizstyle.css");
|
||||
/* a button for the download links */
|
||||
a.download{
|
||||
margin-top:15px;
|
||||
max-width:190px;
|
||||
background-color:#eee;
|
||||
border-color:#888888;
|
||||
color:#333;
|
||||
display:inline-block;
|
||||
vertical-align:middle;
|
||||
text-align:center;
|
||||
text-decoration:none;
|
||||
align-items:flex-start;
|
||||
cursor:default;
|
||||
-webkit-appearence: push-button;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-radius: 5px;
|
||||
font-size: 1em;
|
||||
font-family: inherit;
|
||||
border-color: #000;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
width: 100%;
|
||||
min-height: 30px;
|
||||
}
|
||||
|
||||
/* the bash output looks like different */
|
||||
div.highlight-bash {
|
||||
|
||||
background-color:#eee;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-radius: 5px;
|
||||
border-color: #000;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
4
docs/custom/theme.conf
Normal file
|
@ -0,0 +1,4 @@
|
|||
[theme]
|
||||
inherit = bizstyle
|
||||
stylesheet = custom.css
|
||||
|
59
docs/dynoptiondescription.rst
Normal file
|
@ -0,0 +1,59 @@
|
|||
==========================================================
|
||||
Dynamic option description: :class:`DynOptionDescription`
|
||||
==========================================================
|
||||
|
||||
Dynamic option description
|
||||
==============================================
|
||||
|
||||
Dynamic option description is an :class:`OptionDescription` which multiplies according to the return of a function.
|
||||
|
||||
.. list-table::
|
||||
:widths: 15 45
|
||||
:header-rows: 1
|
||||
|
||||
* - Parameter
|
||||
- Comments
|
||||
|
||||
* - name
|
||||
- The `name` is important to retrieve this option.
|
||||
|
||||
* - doc
|
||||
- The `description` allows the user to understand where this option will be used for.
|
||||
|
||||
* - children
|
||||
- The list of children (Option) include inside.
|
||||
|
||||
Note:: the option can be an :doc:`option` or an other option description
|
||||
|
||||
* - suffixes
|
||||
- Suffixes is a :doc:`calculation` that return the list of suffixes used to create dynamic option description.
|
||||
|
||||
* - properties
|
||||
- A list of :doc:`property` (inside a frozenset().
|
||||
|
||||
Example
|
||||
==============
|
||||
|
||||
Let's try:
|
||||
|
||||
>>> from tiramisu import StrOption, DynOptionDescription, Calculation
|
||||
>>> def return_suffixes():
|
||||
... return ['1', '2']
|
||||
>>> child1 = StrOption('first', 'First basic option ')
|
||||
>>> child2 = StrOption('second', 'Second basic option ')
|
||||
>>> DynOptionDescription('basic ',
|
||||
... 'Basic options ',
|
||||
... [child1, child2],
|
||||
... Calculation(return_suffixes))
|
||||
|
||||
This example will construct:
|
||||
|
||||
- Basic options 1:
|
||||
|
||||
- First basic option 1
|
||||
- Second basic option 1
|
||||
|
||||
- Basic options 2:
|
||||
|
||||
- First basic option 2
|
||||
- Second basic option 2
|
|
@ -1,6 +1,9 @@
|
|||
# Getting started
|
||||
==================================
|
||||
Getting started
|
||||
==================================
|
||||
|
||||
## What is options handling ?
|
||||
What is options handling?
|
||||
=================================
|
||||
|
||||
Due to more and more available options required to set up an operating system,
|
||||
compiler options or whatever, it became quite annoying to hand the necessary
|
||||
|
@ -9,7 +12,8 @@ options.
|
|||
|
||||
To circumvent these problems the configuration control was introduced.
|
||||
|
||||
## What is Tiramisu ?
|
||||
What is Tiramisu?
|
||||
===================
|
||||
|
||||
Tiramisu is an options handler and an options controller, which aims at
|
||||
producing flexible and fast options access. The main advantages are its access
|
||||
|
@ -19,24 +23,35 @@ There is of course type and structure validations, but also
|
|||
validations towards the whole options. Furthermore, options can be reached and
|
||||
changed according to the access rules from nearly everywhere.
|
||||
|
||||
### Installation
|
||||
Installation
|
||||
-------------
|
||||
|
||||
The best way is to use the python [pip](https://pip.pypa.io/en/stable/installing/) installer
|
||||
The best way is to use the python pip_ installer
|
||||
|
||||
.. _pip: https://pip.pypa.io/en/stable/installing/
|
||||
|
||||
And then type:
|
||||
|
||||
```bash
|
||||
$ pip install tiramisu
|
||||
```
|
||||
.. code-block:: bash
|
||||
|
||||
### Advanced users
|
||||
pip install tiramisu
|
||||
|
||||
Advanced users
|
||||
==============
|
||||
|
||||
.. _gettingtiramisu:
|
||||
|
||||
- the library's development homepage is there_
|
||||
|
||||
.. _there: https://forge.cloud.silique.fr/stove/tiramisu/
|
||||
|
||||
To obtain a copy of the sources, check it out from the repository using `git`.
|
||||
We suggest using `git` if one wants to access to the current developments.
|
||||
|
||||
```bash
|
||||
$ git clone https://framagit.org/tiramisu/tiramisu.git
|
||||
```
|
||||
.. code-block:: bash
|
||||
|
||||
git clone https://forge.cloud.silique.fr/stove/tiramisu.git
|
||||
|
||||
This will get you a fresh checkout of the code repository in a local directory
|
||||
named "tiramisu".
|
||||
named ``tiramisu``.
|
||||
|
84
docs/glossary.rst
Normal file
|
@ -0,0 +1,84 @@
|
|||
.. default-role:: literal
|
||||
|
||||
Glossary
|
||||
==========
|
||||
|
||||
.. glossary::
|
||||
|
||||
configuration
|
||||
|
||||
Global configuration object, wich contains the whole configuration
|
||||
options *and* their descriptions (option types and group)
|
||||
|
||||
schema
|
||||
option description
|
||||
|
||||
see :class:`tiramisu.option.OptionDescription`
|
||||
|
||||
The schema of a configuration :
|
||||
|
||||
- the option types
|
||||
|
||||
- how they are organised in groups or even subgroups, that's why we
|
||||
call them **groups** too.
|
||||
|
||||
configuration option
|
||||
|
||||
An option object wich has a name and a value and can be accessed
|
||||
from the configuration object
|
||||
|
||||
access rules
|
||||
|
||||
Global access rules are : :meth:`~config.CommonConfig.read_write()` or
|
||||
:meth:`~config.Config.read_only()`
|
||||
|
||||
default value
|
||||
|
||||
Default value of a configuration option. The default value can be
|
||||
set at instanciation time, or even at any moment. Remember that if
|
||||
you reset the default value, the owner reset to `default`
|
||||
|
||||
freeze
|
||||
|
||||
A whole configuration can be frozen (used in read only access).
|
||||
|
||||
A single option can be frozen too.
|
||||
|
||||
value owner
|
||||
|
||||
When an option is modified, including at the instanciation, we
|
||||
always know who has modified it. It's the owner of the option.
|
||||
|
||||
properties
|
||||
|
||||
an option with properties is a option wich has property like 'hidden' or 'disabled' is an option
|
||||
wich has restricted acces rules.
|
||||
|
||||
hidden option
|
||||
|
||||
a hidden option has a different behaviour on regards to the access
|
||||
of the value in the configuration.
|
||||
|
||||
disabled option
|
||||
|
||||
a disabled option has a different behaviour on regards to the access
|
||||
of the value in the configuration.
|
||||
|
||||
mandatory option
|
||||
|
||||
A mandatory option is a configuration option wich value has to be
|
||||
set, that is the default value cannot be `None`.
|
||||
|
||||
consistency
|
||||
|
||||
Preserving the consistency in a whole configuration is a tricky thing,
|
||||
tiramisu takes care of it for you.
|
||||
|
||||
context
|
||||
|
||||
The context is a :class:`tiramisu.setting.Setting()` object in the
|
||||
configuration that enables us to access to the global properties
|
||||
|
||||
for example the `read_write` or `read_only` :term:`access rules`
|
||||
|
||||
|
BIN
docs/images/firefox_preferences.png
Normal file
After Width: | Height: | Size: 81 KiB |
|
@ -1,36 +1,49 @@
|
|||
![Logo Tiramisu](../logo.png "logo Tiramisu")
|
||||
.. default-role:: literal
|
||||
|
||||
# Python3 Tiramisu library user documentation
|
||||
.. meta::
|
||||
|
||||
## The tasting of `Tiramisu` --- `user documentation`
|
||||
:description: python tiramisu library user documentation
|
||||
:keywords: python, tiramisu, tutorial
|
||||
|
||||
Tiramisu:
|
||||
.. title:: Tiramisu
|
||||
|
||||
- is a cool, refreshing Italian dessert,
|
||||
- it is also an [options controller tool](http://en.wikipedia.org/wiki/Configuration_management#Overview)
|
||||
The tasting of `Tiramisu` --- `user documentation`
|
||||
===================================================
|
||||
|
||||
It's a pretty small, local (that is, straight on the operating system) options handler and controller.
|
||||
.. image:: logo.png
|
||||
:height: 150px
|
||||
|
||||
`Tiramisu`
|
||||
|
||||
- is a cool, refreshing Italian dessert,
|
||||
|
||||
- it is also an `options controller tool`_.
|
||||
|
||||
.. _`options controller tool`: http://en.wikipedia.org/wiki/Configuration_management#Overview
|
||||
|
||||
|
||||
It's a pretty small, local (that is, straight on the operating system) options
|
||||
handler and controller.
|
||||
|
||||
- [Getting started](gettingstarted.md)
|
||||
- [The Config](config.md)
|
||||
- [Browse the Config](browse.md)
|
||||
- [Manage values](api_value.md)
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
gettingstarted
|
||||
config
|
||||
browse
|
||||
api_value
|
||||
api_property
|
||||
storage
|
||||
application
|
||||
quiz
|
||||
glossary
|
||||
|
||||
External project:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
cmdline_parser
|
||||
.. External project:
|
||||
..
|
||||
.. .. toctree::
|
||||
.. :maxdepth: 2
|
||||
..
|
||||
.. cmdline_parser
|
||||
|
||||
.. FIXME ca veut rien dire : "AssertionError: type <class 'tiramisu.autolib.Calculation'> invalide pour des propriétés pour protocols, doit être un frozenset"
|
||||
|
48
docs/leadership.rst
Normal file
|
@ -0,0 +1,48 @@
|
|||
==========================================================
|
||||
Leadership OptionDescription: :class:`Leadership`
|
||||
==========================================================
|
||||
|
||||
A leadership is a special `OptionDescription` that wait a leader and one or multiple followers.
|
||||
|
||||
Leader and follower are multi option. The difference is that the length is defined by the length of the option leader.
|
||||
|
||||
If the length of leader is 3, all followers have also length 3.
|
||||
|
||||
An other different is that the follower is isolate. That means that you can only change on value on a specified index in the list of it's values.
|
||||
If a value is mark as modified in a specified index, that not affect the other values in other index.
|
||||
|
||||
The leadership
|
||||
==============================================
|
||||
|
||||
.. list-table::
|
||||
:widths: 15 45
|
||||
:header-rows: 1
|
||||
|
||||
* - Parameter
|
||||
- Comments
|
||||
|
||||
* - name
|
||||
- The `name` is important to retrieve this option.
|
||||
|
||||
* - doc
|
||||
- The `description` allows the user to understand where this option will be used for.
|
||||
|
||||
* - children
|
||||
- The list of children (Option) include inside.
|
||||
|
||||
Note:: the option has to be multi or submulti option and not other option description.
|
||||
|
||||
* - properties
|
||||
- A list of :doc:`property` (inside a frozenset().
|
||||
|
||||
Example
|
||||
====================
|
||||
|
||||
Let's try:
|
||||
|
||||
>>> from tiramisu import StrOption, Leadership
|
||||
>>> users = StrOption('users', 'User', multi=True)
|
||||
>>> passwords = StrOption('passwords', 'Password', multi=True)
|
||||
>>> Leadership('users',
|
||||
... 'User allow to connect',
|
||||
... [users, passwods])
|
BIN
docs/logo.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
136
docs/option.rst
Normal file
|
@ -0,0 +1,136 @@
|
|||
==================================
|
||||
Instanciate an option
|
||||
==================================
|
||||
|
||||
Option
|
||||
========
|
||||
|
||||
.. list-table::
|
||||
:widths: 15 45
|
||||
:header-rows: 1
|
||||
|
||||
* - Parameter
|
||||
- Comments
|
||||
|
||||
* - name
|
||||
- The `name` is important to retrieve this option.
|
||||
|
||||
* - doc
|
||||
- The `description` allows the user to understand where this option will be used for.
|
||||
|
||||
* - multi
|
||||
- There are cases where it can be interesting to have a list of values rather than just one.
|
||||
|
||||
* - default
|
||||
- For each option, we can defined a default value. This value will be the value of this option until user customize it.
|
||||
|
||||
This default value is store directly in the option. So we can, at any moment we can go back to the default value.
|
||||
|
||||
The default value can be a :doc:`calculation`.
|
||||
|
||||
* - default_multi
|
||||
- A second default value is available for multi option, `default_multi`. This value is used when we add new value without specified a value.
|
||||
|
||||
This `default_multi` must not be a list in multi purpose. For submulti, it has to be a list.
|
||||
|
||||
The default_multi value can be a :doc:`calculation`.
|
||||
|
||||
* - validators
|
||||
- A list of :doc:`validator`.
|
||||
|
||||
* - warnings_only
|
||||
- Only emit warnings if not type validation is invalid.
|
||||
|
||||
* - properties
|
||||
- A list of :doc:`property` (inside a frozenset().
|
||||
|
||||
|
||||
Examples
|
||||
==========
|
||||
|
||||
Let's try a simple option:
|
||||
|
||||
>>> from tiramisu import StrOption
|
||||
>>> StrOption('welcome',
|
||||
... 'Welcome message to the user login')
|
||||
|
||||
|
||||
Add a default value:
|
||||
|
||||
>>> from tiramisu import StrOption
|
||||
>>> StrOption('welcome',
|
||||
... 'Welcome message to the user login',
|
||||
... 'Hey guys, welcome here!')
|
||||
|
||||
Or a calculated default value:
|
||||
|
||||
>>> from tiramisu import StrOption, Calculation
|
||||
>>> def get_value():
|
||||
... return 'Hey guys, welcome here'
|
||||
>>> StrOption('welcome',
|
||||
... 'Welcome message to the user login',
|
||||
... Calculation(get_value))
|
||||
|
||||
|
||||
A multi option. In this case, the default value has to be a list:
|
||||
|
||||
>>> from tiramisu import StrOption
|
||||
>>> StrOption('shopping_list',
|
||||
... 'The shopping list',
|
||||
... ['1 kilogram of carrots', 'leeks', '1 kilogram of potatos'],
|
||||
... multi=True)
|
||||
|
||||
The option could be a list of list, which is could submulti:
|
||||
|
||||
>>> from tiramisu import StrOption, submulti
|
||||
>>> StrOption('shopping_list',
|
||||
... 'The shopping list',
|
||||
... [['1 kilogram of carrots', 'leeks', '1 kilogram of potatos'],
|
||||
... ['milk', 'eggs']],
|
||||
... multi=submulti)
|
||||
|
||||
The default value can be a :doc:`calculation`. For a multi, the function have to return a list or have to be in a list:
|
||||
|
||||
>>> from tiramisu import StrOption, Calculation
|
||||
>>> def get_values():
|
||||
... return ['1 kilogram of carrots', 'leeks', '1 kilogram of potatos']
|
||||
>>> StrOption('shopping_list',
|
||||
... 'The shopping list',
|
||||
... Calculation(get_values),
|
||||
... multi=True)
|
||||
|
||||
or
|
||||
|
||||
>>> from tiramisu import StrOption, Calculation
|
||||
>>> def get_a_value():
|
||||
... return 'leeks'
|
||||
>>> StrOption('shopping_list',
|
||||
... 'The shopping list',
|
||||
... ['1 kilogram of carrots', Calculation(get_a_value), '1 kilogram of potatos'],
|
||||
... multi=True)
|
||||
|
||||
Add a default_multi:
|
||||
|
||||
>>> from tiramisu import StrOption, submulti
|
||||
>>> StrOption('shopping_list',
|
||||
... 'The shopping list',
|
||||
... ['1 kilogram of carrots', 'leeks', '1 kilogram of potatos'],
|
||||
... default_multi='some vegetables',
|
||||
... multi=True)
|
||||
>>> StrOption('shopping_list',
|
||||
... 'The shopping list',
|
||||
... [['1 kilogram of carrots', 'leeks', '1 kilogram of potatos'],
|
||||
... ['milk', 'eggs']],
|
||||
... default_multi=['some', 'vegetables'],
|
||||
... multi=submulti)
|
||||
|
||||
Or calculated default_multi:
|
||||
|
||||
>>> from tiramisu import StrOption
|
||||
>>> def get_a_value():
|
||||
... return 'some vegetables'
|
||||
>>> StrOption('shopping_list',
|
||||
... 'The shopping list',
|
||||
... ['1 kilogram of carrots', 'leeks', '1 kilogram of potatos'],
|
||||
... default_multi=Calculation(get_a_value),
|
||||
... multi=True)
|
38
docs/optiondescription.rst
Normal file
|
@ -0,0 +1,38 @@
|
|||
==============================================
|
||||
Generic container: :class:`OptionDescription`
|
||||
==============================================
|
||||
|
||||
Option description
|
||||
===================================
|
||||
|
||||
.. list-table::
|
||||
:widths: 15 45
|
||||
:header-rows: 1
|
||||
|
||||
* - Parameter
|
||||
- Comments
|
||||
|
||||
* - name
|
||||
- The `name` is important to retrieve this option.
|
||||
|
||||
* - doc
|
||||
- The `description` allows the user to understand where this option will be used for.
|
||||
|
||||
* - children
|
||||
- The list of children (Option) include inside.
|
||||
.. note:: the option can be an :doc:`option` or an other option description
|
||||
|
||||
* - properties
|
||||
- A list of :doc:`property` (inside a frozenset().
|
||||
|
||||
Examples
|
||||
==============
|
||||
|
||||
>>> from tiramisu import StrOption, OptionDescription
|
||||
>>> child1 = StrOption('first', 'First basic option')
|
||||
>>> child2 = StrOption('second', 'Second basic option')
|
||||
>>> child3 = StrOption('third', 'Third basic option')
|
||||
>>> od1 = OptionDescription('od1', 'First option description', [child3])
|
||||
>>> OptionDescription('basic',
|
||||
... 'Basic options',
|
||||
... [child1, child2, od1])
|
330
docs/options.rst
Normal file
|
@ -0,0 +1,330 @@
|
|||
==================================
|
||||
Default Options type
|
||||
==================================
|
||||
|
||||
Basic options
|
||||
==================================
|
||||
|
||||
Options
|
||||
-----------
|
||||
|
||||
.. list-table::
|
||||
:widths: 20 40 40
|
||||
:header-rows: 1
|
||||
|
||||
* - Type
|
||||
- Comments
|
||||
- Extra parameters
|
||||
|
||||
* - StrOption
|
||||
- Option that accept any textual data in Tiramisu.
|
||||
-
|
||||
|
||||
* - IntOption
|
||||
- Option that accept any integers number in Tiramisu.
|
||||
-
|
||||
- min_number
|
||||
- max_number
|
||||
|
||||
* - FloatOption
|
||||
- Option that accept any floating point number in Tiramisu.
|
||||
-
|
||||
|
||||
* - BoolOption
|
||||
- Boolean values are the two constant objects False and True.
|
||||
-
|
||||
|
||||
Examples
|
||||
------------
|
||||
|
||||
Textual option:
|
||||
|
||||
>>> from tiramisu import StrOption
|
||||
>>> StrOption('str', 'str', 'value')
|
||||
>>> try:
|
||||
... StrOption('str', 'str', 1)
|
||||
... except ValueError as err:
|
||||
... print(err)
|
||||
...
|
||||
"1" is an invalid string for "str"
|
||||
|
||||
Integer option:
|
||||
|
||||
>>> from tiramisu import IntOption
|
||||
>>> IntOption('int', 'int', 1)
|
||||
>>> IntOption('int', 'int', 10, min_number=10, max_number=15)
|
||||
>>> try:
|
||||
... IntOption('int', 'int', 16, max_number=15)
|
||||
... except ValueError as err:
|
||||
... print(err)
|
||||
...
|
||||
"16" is an invalid integer for "int", value must be less than "15"
|
||||
|
||||
.. note:: If `warnings_only` parameter it set to True, it will only emit a warning.
|
||||
|
||||
Floating point number option:
|
||||
|
||||
>>> from tiramisu import FloatOption
|
||||
>>> FloatOption('float', 'float', 10.1)
|
||||
|
||||
Boolean option:
|
||||
|
||||
>>> from tiramisu import BoolOption
|
||||
>>> BoolOption('bool', 'bool', True)
|
||||
>>> BoolOption('bool', 'bool', False)
|
||||
|
||||
Network options
|
||||
==================================
|
||||
|
||||
.. list-table::
|
||||
:widths: 20 40 40
|
||||
:header-rows: 1
|
||||
|
||||
* - Type
|
||||
- Comments
|
||||
- Extra parameters
|
||||
|
||||
* - IPOption
|
||||
- An Internet Protocol address (IP address) is a numerical label assigned to each device connected to a computer network that uses the Internet Protocol for communication.
|
||||
|
||||
This option only support version 4 of the Internet Protocol.
|
||||
-
|
||||
- private_only: restrict to only private IPv4 address
|
||||
- allow_reserved: allow the IETF reserved address
|
||||
- cidr: Classless Inter-Domain Routing (CIDR) is a method for allocating IP addresses and IP routing, such as 192.168.0.1/24
|
||||
|
||||
* - NetworkOption
|
||||
- IP networks may be divided into subnetworks
|
||||
-
|
||||
- cidr: Classless Inter-Domain Routing (CIDR) is a method for allocating IP addresses and IP routing, such as 192.168.0.0/24
|
||||
|
||||
* - NetmaskOption
|
||||
- For IPv4, a network may also be characterized by its subnet mask or netmask. This option allow you to enter a netmask.
|
||||
-
|
||||
|
||||
* - BroadcastOption
|
||||
- The last address within a network broadcast transmission to all hosts on the link. This option allow you to enter a broadcast:
|
||||
-
|
||||
|
||||
* - PortOption
|
||||
- A port is a network communication endpoint. It's a string object
|
||||
-
|
||||
- allow_range: allow is a list of port where we specified first port and last port number with the separator is `:`
|
||||
- allow_zero: allow the port 0
|
||||
- allow_wellknown: by default, the well-known ports (also known as system ports) those from 1 through 1023 are allowed, you can disabled it
|
||||
- allow_registred: by default, the registered ports are those from 1024 through 49151 are allowed, you can disabled it
|
||||
- allow_private: allow dynamic or private ports, which are those from 49152 through 65535, one common use for this range is for ephemeral ports
|
||||
|
||||
|
||||
Examples
|
||||
-------------------------------------------
|
||||
|
||||
>>> from tiramisu import IPOption
|
||||
>>> IPOption('ip', 'ip', '192.168.0.24')
|
||||
>>> IPOption('ip', 'ip', '1.1.1.1')
|
||||
>>> try:
|
||||
... IPOption('ip', 'ip', '1.1.1.1', private_only=True)
|
||||
... except ValueError as err:
|
||||
... print(err)
|
||||
...
|
||||
"1.1.1.1" is an invalid IP for "ip", must be private IP
|
||||
|
||||
.. note:: If `warnings_only` parameter it set to True, it will only emit a warning.
|
||||
|
||||
>>> from tiramisu import IPOption
|
||||
>>> try:
|
||||
... IPOption('ip', 'ip', '255.255.255.255')
|
||||
... except ValueError as err:
|
||||
... print(err)
|
||||
...
|
||||
"255.255.255.255" is an invalid IP for "ip", mustn't be reserved IP
|
||||
>>> IPOption('ip', 'ip', '255.255.255.255', allow_reserved=True)
|
||||
|
||||
.. note:: If `warnings_only` parameter it set to True, it will only emit a warning.
|
||||
|
||||
>>> from tiramisu import IPOption
|
||||
>>> IPOption('ip', 'ip', '192.168.0.1/24', cidr=True)
|
||||
>>> try:
|
||||
... IPOption('ip', 'ip', '192.168.0.0/24', cidr=True)
|
||||
... except ValueError as err:
|
||||
... print(err)
|
||||
...
|
||||
"192.168.0.0/24" is an invalid IP for "ip", it's in fact a network address
|
||||
|
||||
>>> from tiramisu import NetworkOption
|
||||
>>> NetworkOption('net', 'net', '192.168.0.0')
|
||||
>>> NetworkOption('net', 'net', '192.168.0.0/24', cidr=True)
|
||||
>>> NetmaskOption('mask', 'mask', '255.255.255.0')
|
||||
|
||||
>>> from tiramisu import BroadcastOption
|
||||
>>> BroadcastOption('bcast', 'bcast', '192.168.0.254')
|
||||
|
||||
>>> from tiramisu import PortOption
|
||||
>>> PortOption('port', 'port', '80')
|
||||
>>> PortOption('port', 'port', '2000', allow_range=True)
|
||||
>>> PortOption('port', 'port', '2000:3000', allow_range=True)
|
||||
>>> from tiramisu import PortOption
|
||||
>>> try:
|
||||
... PortOption('port', 'port', '0')
|
||||
... except ValueError as err:
|
||||
... print(err)
|
||||
...
|
||||
"0" is an invalid port for "port", must be between 1 and 49151
|
||||
>>> PortOption('port', 'port', '0', allow_zero=True)
|
||||
|
||||
.. note:: This option affect the minimal and maximal port number, if `warnings_only` parameter it set to True, it will only emit a warning.
|
||||
|
||||
>>> from tiramisu import PortOption
|
||||
>>> PortOption('port', 'port', '80')
|
||||
>>> try:
|
||||
... PortOption('port', 'port', '80', allow_wellknown=False)
|
||||
... except ValueError as err:
|
||||
... print(err)
|
||||
...
|
||||
"80" is an invalid port for "port", must be between 1024 and 49151
|
||||
|
||||
.. note:: This option affect the minimal and maximal port number, if `warnings_only` parameter it set to True, it will only emit a warning.
|
||||
|
||||
>>> from tiramisu import PortOption
|
||||
>>> PortOption('port', 'port', '1300')
|
||||
>>> try:
|
||||
... PortOption('port', 'port', '1300', allow_registred=False)
|
||||
... except ValueError as err:
|
||||
... print(err)
|
||||
...
|
||||
"1300" is an invalid port for "port", must be between 1 and 1023
|
||||
|
||||
.. note:: This option affect the minimal and maximal port number, if `warnings_only` parameter it set to True, it will only emit a warning.
|
||||
|
||||
>>> from tiramisu import PortOption
|
||||
>>> try:
|
||||
... PortOption('port', 'port', '64000')
|
||||
... except ValueError as err:
|
||||
... print(err)
|
||||
...
|
||||
"64000" is an invalid port for "port", must be between 1 and 49151
|
||||
>>> PortOption('port', 'port', '64000', allow_private=True)
|
||||
|
||||
.. note:: This option affect the minimal and maximal port number, if `warnings_only` parameter it set to True, it will only emit a warning.
|
||||
|
||||
Internet options
|
||||
==================================
|
||||
|
||||
.. list-table::
|
||||
:widths: 20 40 40
|
||||
:header-rows: 1
|
||||
|
||||
* - Type
|
||||
- Comments
|
||||
- Extra parameters
|
||||
|
||||
* - DomainnameOption
|
||||
- Domain names are used in various networking contexts and for application-specific naming and addressing purposes.
|
||||
-
|
||||
- type: There is three type for a domain name:
|
||||
|
||||
- "domainname" (default): lowercase, number, "-" and "." characters are allowed, this must have at least one "."
|
||||
- "hostname": lowercase, number and "-" characters are allowed, the maximum length is 63 characters
|
||||
- "netbios": lowercase, number and "-" characters are allowed, the maximum length is 15 characters
|
||||
|
||||
- allow_ip: the option can contain a domain name or an IP, in this case, IP is validate has IPOption would do.
|
||||
- allow_cidr_network: the option can contain a CIDR network
|
||||
- allow_without_dot: a domain name with domainname's type must have a dot, if active, we can set a domainname or an hostname
|
||||
- allow_startswith_dot: a domain name with domainname's type mustn't start by a dot, .example.net is not a valid domain, in some case it could be interesting to allow domain name starts by a dot (for ACL in Squid, no proxy option in Firefox, ...)
|
||||
|
||||
* - URLOption
|
||||
- An Uniform Resource Locator is, in fact, a string starting with http:// or https://, a DomainnameOption, optionaly ':' and a PortOption, and finally filename
|
||||
- See PortOption and DomainnameOption parameters
|
||||
|
||||
* - EmailOption
|
||||
- Electronic mail (email or e-mail) is a method of exchanging messages ("mail") between people using electronic devices.
|
||||
-
|
||||
|
||||
|
||||
Examples
|
||||
-----------------------------------------------
|
||||
|
||||
>>> from tiramisu import DomainnameOption
|
||||
>>> DomainnameOption('domain', 'domain', 'foo.example.net')
|
||||
>>> DomainnameOption('domain', 'domain', 'foo', type='hostname')
|
||||
|
||||
.. note:: If `warnings_only` parameter it set to True, it will raise if length is incorrect by only emit a warning character is not correct.
|
||||
|
||||
>>> from tiramisu import DomainnameOption
|
||||
>>> DomainnameOption('domain', 'domain', 'foo.example.net', allow_ip=True)
|
||||
>>> DomainnameOption('domain', 'domain', '192.168.0.1', allow_ip=True)
|
||||
>>> DomainnameOption('domain', 'domain', 'foo.example.net', allow_cidr_network=True)
|
||||
>>> DomainnameOption('domain', 'domain', '192.168.0.0/24', allow_cidr_network=True)
|
||||
>>> DomainnameOption('domain', 'domain', 'foo.example.net', allow_without_dot=True)
|
||||
>>> DomainnameOption('domain', 'domain', 'foo', allow_without_dot=True)
|
||||
>>> DomainnameOption('domain', 'domain', 'example.net', allow_startswith_dot=True)
|
||||
>>> DomainnameOption('domain', 'domain', '.example.net', allow_startswith_dot=True)
|
||||
|
||||
>>> from tiramisu import URLOption
|
||||
>>> URLOption('url', 'url', 'http://foo.example.fr/index.php')
|
||||
>>> URLOption('url', 'url', 'https://foo.example.fr:4200/index.php?login=foo&pass=bar')
|
||||
|
||||
>>> from tiramisu import EmailOption
|
||||
>>> EmailOption('mail', 'mail', 'foo@example.net')
|
||||
|
||||
Unix options
|
||||
===============
|
||||
|
||||
.. list-table::
|
||||
:widths: 20 40
|
||||
:header-rows: 1
|
||||
|
||||
* - Type
|
||||
- Comments
|
||||
|
||||
* - UsernameOption
|
||||
- An unix username option is a 32 characters maximum length with lowercase ASCII characters, number, '_' or '-'. The username have to start with lowercase ASCII characters or "_".
|
||||
|
||||
* - GroupnameOption
|
||||
- Same conditions has username
|
||||
|
||||
* - PasswordOption
|
||||
- Simple string with no other restriction:
|
||||
|
||||
* - FilenameOption
|
||||
- For this option, only lowercase and uppercas ASCII character, "-", ".", "_", "~", and "/" are allowed.
|
||||
|
||||
>>> from tiramisu import UsernameOption
|
||||
>>> UsernameOption('user', 'user', 'my_user')
|
||||
|
||||
>>> from tiramisu import GroupnameOption
|
||||
>>> GroupnameOption('group', 'group', 'my_group')
|
||||
|
||||
>>> from tiramisu import PasswordOption
|
||||
>>> PasswordOption('pass', 'pass', 'oP$¨1jiJie')
|
||||
|
||||
>>> from tiramisu import FilenameOption
|
||||
>>> FilenameOption('file', 'file', '/etc/tiramisu/tiramisu.conf')
|
||||
|
||||
Date option
|
||||
=============
|
||||
|
||||
Date option waits for a date with format YYYY-MM-DD:
|
||||
|
||||
>>> from tiramisu import DateOption
|
||||
>>> DateOption('date', 'date', '2019-10-30')
|
||||
|
||||
Choice option: :class:`ChoiceOption`
|
||||
======================================
|
||||
|
||||
Option that only accepts a list of possible choices.
|
||||
|
||||
For example, we just want allowed 1 or 'see later':
|
||||
|
||||
>>> from tiramisu import ChoiceOption
|
||||
>>> ChoiceOption('choice', 'choice', (1, 'see later'), 1)
|
||||
>>> ChoiceOption('choice', 'choice', (1, 'see later'), 'see later')
|
||||
|
||||
Any other value isn't allowed:
|
||||
|
||||
>>> try:
|
||||
... ChoiceOption('choice', 'choice', (1, 'see later'), "i don't know")
|
||||
... except ValueError as err:
|
||||
... print(err)
|
||||
...
|
||||
"i don't know" is an invalid choice for "choice", only "1" and "see later" are allowed
|
125
docs/own_option.rst
Normal file
|
@ -0,0 +1,125 @@
|
|||
======================================
|
||||
Create it's own option
|
||||
======================================
|
||||
|
||||
Generic regexp option: :class:`RegexpOption`
|
||||
====================================================
|
||||
|
||||
Use `RegexpOption` to create custom option is very simple.
|
||||
|
||||
You just have to create an object that inherits from `RegexpOption` and that has the following class attributes:
|
||||
|
||||
- __slots__: with new data members (the values should always be `tuple()`)
|
||||
- _type = with a name
|
||||
- _display_name: with the display name (for example in error message)
|
||||
- _regexp: with a compiled regexp
|
||||
|
||||
Here an example to an option that only accept string with on lowercase ASCII vowel characters:
|
||||
|
||||
.. literalinclude:: src/own_option.py
|
||||
:lines: 3-11
|
||||
:linenos:
|
||||
|
||||
Let's try our object:
|
||||
|
||||
>>> VowelOption('vowel', 'Vowel', 'aae')
|
||||
<VowelOption object at 0x7feb2779c050>
|
||||
>>> try:
|
||||
... VowelOption('vowel', 'Vowel', 'oooups')
|
||||
... except ValueError as err:
|
||||
... print(err)
|
||||
...
|
||||
"oooups" is an invalid string with vowel for "Vowel"
|
||||
|
||||
Create you own option
|
||||
=================================
|
||||
|
||||
An option always inherits from `Option` object. This object has the following class attributes:
|
||||
|
||||
- __slots__: with new data members (the values should always be `tuple()`)
|
||||
- _type = with a name
|
||||
- _display_name: with the display name (for example in error message)
|
||||
|
||||
Here an example to an lipogram option:
|
||||
|
||||
.. literalinclude:: src/own_option2.py
|
||||
:lines: 3-15
|
||||
:linenos:
|
||||
|
||||
First of all we want to add a custom parameter to ask the minimum length (`min_len`) of the value:
|
||||
|
||||
.. literalinclude:: src/own_option2.py
|
||||
:lines: 16-20
|
||||
:linenos:
|
||||
|
||||
We have a first validation method. In this method, we verify that the value is a string and that there is no "e" on it:
|
||||
|
||||
.. literalinclude:: src/own_option2.py
|
||||
:lines: 22-29
|
||||
:linenos:
|
||||
|
||||
Even if user set warnings_only attribute, this method will raise.
|
||||
|
||||
Finally we add a method to valid the value length. If `warnings_only` is set to True, a warning will be emit:
|
||||
|
||||
.. literalinclude:: src/own_option2.py
|
||||
:lines: 31-43
|
||||
:linenos:
|
||||
|
||||
Let's test it:
|
||||
|
||||
1. the character "e" is in the value:
|
||||
|
||||
>>> try:
|
||||
... LipogramOption('lipo',
|
||||
... 'Lipogram',
|
||||
... 'I just want to add a quality string that has no bad characters')
|
||||
... except ValueError as err:
|
||||
... print(err)
|
||||
...
|
||||
"I just want to add a quality string that has no bad characters" is an invalid lipogram for "Lipogram", Perec wrote a book without any "e", you could not do it in a simple sentence?
|
||||
|
||||
2. the character "e" is in the value and warnings_only is set to True:
|
||||
|
||||
>>> try:
|
||||
... LipogramOption('lipo',
|
||||
... 'Lipogram',
|
||||
... 'I just want to add a quality string that has no bad characters',
|
||||
... warnings_only=True)
|
||||
... except ValueError as err:
|
||||
... print(err)
|
||||
...
|
||||
"I just want to add a quality string that has no bad characters" is an invalid lipogram for "Lipogram", Perec wrote a book without any "e", you could not do it in a simple sentence?
|
||||
|
||||
3. the value is too short
|
||||
|
||||
>>> try:
|
||||
... LipogramOption('lipo',
|
||||
... 'Lipogram',
|
||||
... 'I just want to add a quality string that has no bad symbols')
|
||||
... except ValueError as err:
|
||||
... print(err)
|
||||
...
|
||||
"I just want to add a quality string that has no bad symbols" is an invalid lipogram for "Lipogram", you must have at least 100 characters in the sentence
|
||||
|
||||
4. the value is too short and warnings_only is set to True:
|
||||
|
||||
>>> warnings.simplefilter('always', ValueWarning)
|
||||
>>> with warnings.catch_warnings(record=True) as warn:
|
||||
... LipogramOption('lipo',
|
||||
... 'Lipogram',
|
||||
... 'I just want to add a quality string that has no bad symbols',
|
||||
... warnings_only=True)
|
||||
... if warn:
|
||||
... print(warn[0].message)
|
||||
...
|
||||
attention, "I just want to add a quality string that has no bad symbols" could be an invalid lipogram for "Lipogram", it would be better to have at least 100 characters in the sentence
|
||||
|
||||
5. set minimum length to 50 characters, the value is valid:
|
||||
|
||||
>>> LipogramOption('lipo',
|
||||
... 'Lipogram',
|
||||
... 'I just want to add a quality string that has no bad symbols',
|
||||
... min_len=50)
|
||||
|
||||
|
126
docs/property.rst
Normal file
|
@ -0,0 +1,126 @@
|
|||
==================================
|
||||
Properties
|
||||
==================================
|
||||
|
||||
What are properties?
|
||||
==================================
|
||||
|
||||
The properties are a central element of Tiramisu.
|
||||
|
||||
Properties change the behavior of an option or make it unavailable.
|
||||
|
||||
Read only and read write
|
||||
=================================
|
||||
|
||||
Config can be in two defaut mode:
|
||||
|
||||
read_only
|
||||
-----------------------
|
||||
|
||||
You can get all variables in a `Config` that not disabled.
|
||||
|
||||
Off course, as the Config is read only, you cannot set any value to any option.
|
||||
Only value with :doc`calculation` can change value.
|
||||
You cannot access to mandatory variable without values. Verify that all values is set before change mode.
|
||||
|
||||
read_write
|
||||
-----------------------
|
||||
|
||||
You can get all options not disabled and not hidden. You can also set all variables not frozen.
|
||||
|
||||
Common properties
|
||||
=================================
|
||||
|
||||
hidden
|
||||
-----------------------
|
||||
|
||||
Option with this property can only get value in read only mode (or for a :doc:`calculation`).
|
||||
This property is used for option that user cannot modifify it's value (for example if it's value is calculated).
|
||||
|
||||
disabled
|
||||
-----------------------
|
||||
|
||||
We never can access to option with this property.
|
||||
|
||||
frozen
|
||||
-----------------------
|
||||
|
||||
Options with this property cannot be modified.
|
||||
|
||||
Special option properties
|
||||
=================================
|
||||
|
||||
mandatory
|
||||
-----------------------
|
||||
|
||||
You should set value for option with this properties. In read only mode we cannot access to this option if no value is set.
|
||||
|
||||
empty or notempty
|
||||
-----------------------
|
||||
|
||||
Only used for multi option that are not a follower.
|
||||
|
||||
Mandatory for a multi means that you cannot add None as a value. But value [] is allowed. This is not permit with `empty` property.
|
||||
|
||||
A multi option has automaticly `empty` property. If you don't want allow empty option, just add `notempty` property when you create the option.
|
||||
|
||||
unique or notunique
|
||||
-----------------------
|
||||
|
||||
Only used for multi option that are not a follower.
|
||||
|
||||
Raise ValueError if a value is set twice or more in a multi Option.
|
||||
|
||||
A multi option has automaticly `unique` property. If you want allow duplication in option, just add `notunique` property when you create the option.
|
||||
|
||||
permissive
|
||||
-----------------------
|
||||
|
||||
Option with 'permissive' cannot raise PropertiesOptionError for properties set in permissive.
|
||||
|
||||
Config with 'permissive', whole option in this config cannot raise PropertiesOptionError for properties set in permissive.
|
||||
|
||||
Special Config properties
|
||||
=================================
|
||||
|
||||
cache
|
||||
-----------------------
|
||||
|
||||
Enable cache settings and values.
|
||||
|
||||
expire
|
||||
-----------------------
|
||||
|
||||
Enable settings and values in cache expire after `expiration_time` (by default 5 seconds).
|
||||
|
||||
everything_frozen
|
||||
-----------------------
|
||||
|
||||
Whole option in config are frozen (even if option have not frozen property).
|
||||
|
||||
validator
|
||||
-----------------------
|
||||
|
||||
Launch validator set by user in option (this property has no effect for option validation and second level validation).
|
||||
|
||||
warnings
|
||||
-----------------------
|
||||
|
||||
Display warnings during validation.
|
||||
|
||||
demoting_error_warning
|
||||
-----------------------
|
||||
|
||||
All value's errors are convert to warning (ValueErrorWarning).
|
||||
|
||||
Own properties
|
||||
=================================
|
||||
|
||||
There are no specific instructions for creating a property. Just add a string as property create a new property.
|
||||
|
||||
|
||||
|
||||
.. #FIXME
|
||||
.. FORBIDDEN_SET_PERMISSIVES = frozenset(['force_default_on_freeze',
|
||||
.. 'force_metaconfig_on_freeze',
|
||||
.. 'force_store_value'])
|
158
docs/quiz.rst
Normal file
|
@ -0,0 +1,158 @@
|
|||
==================================
|
||||
Bonus: Let's create a quiz!
|
||||
==================================
|
||||
|
||||
Creating our quiz
|
||||
==================================
|
||||
|
||||
Of course Tiramisu is great to handle options, but here's a little thing you can
|
||||
do if you're just bored and don't know what to do.
|
||||
|
||||
So, let's create a quiz.
|
||||
|
||||
First, as always, let's import everything we need from Tiramisu and create our options:
|
||||
|
||||
.. literalinclude:: src/quiz.py
|
||||
:lines: 1-6
|
||||
:linenos:
|
||||
|
||||
We make a dictionary with all questions, proposals and answer:
|
||||
|
||||
.. literalinclude:: src/quiz.py
|
||||
:lines: 20-35
|
||||
:linenos:
|
||||
|
||||
Now build our Config.
|
||||
|
||||
We have to create question option.
|
||||
|
||||
Just after, we're going to define the correct answer in a second option.
|
||||
|
||||
The answer is frozen so that when it is set, it cannot change.
|
||||
|
||||
And finally a last option that will verify if the answer is correct.
|
||||
|
||||
.. literalinclude:: src/quiz.py
|
||||
:lines: 38-59
|
||||
:linenos:
|
||||
|
||||
The `verif` option will us a function that will verify if the answer given by the user
|
||||
(which will become the value of `question`) is the same as the correct answer (which is the value
|
||||
of `answer`). Here is this function (of course you have to declare at the begining of your code,
|
||||
before your options) :
|
||||
|
||||
.. literalinclude:: src/quiz.py
|
||||
:lines: 12-13
|
||||
:linenos:
|
||||
|
||||
Pretty simple.
|
||||
|
||||
At least we're done with our questions. Let's just create one last option.
|
||||
This option calculate the result of the students' answers:
|
||||
|
||||
.. literalinclude:: src/quiz.py
|
||||
:lines: 16-17, 62-65
|
||||
:linenos:
|
||||
|
||||
Now we just have to create our OptionDescription and Config (well it's a MetaConfig
|
||||
here, but we'll see this later)...
|
||||
|
||||
.. literalinclude:: src/quiz.py
|
||||
:lines: 66, 9, 67
|
||||
:linenos:
|
||||
|
||||
... and add some loops to run our quiz!
|
||||
|
||||
.. literalinclude:: src/quiz.py
|
||||
:lines: 70-104
|
||||
:linenos:
|
||||
|
||||
Display results for teacher:
|
||||
|
||||
.. literalinclude:: src/quiz.py
|
||||
:lines: 107-117
|
||||
:linenos:
|
||||
|
||||
|
||||
Now let's play !
|
||||
|
||||
.. literalinclude:: src/quiz.py
|
||||
:lines: 120-132
|
||||
|
||||
Download the :download:`full code <src/quiz.py>`
|
||||
|
||||
Hey, that was easy ! Almost like I already knew the answers... Oh wait...
|
||||
|
||||
Get players results
|
||||
==================================
|
||||
|
||||
Now that you have your quiz, you can play with friends! And what's better than playing
|
||||
with friends? Crushing them by comparing your scores of course!
|
||||
You may have noticed that the previous code had some storage instructions. Now we can
|
||||
create a score board that will give each player's latest score with their errors !
|
||||
|
||||
We created a meta config that will be used as a base for all configs we will create :
|
||||
each time a new player name will be entered, a new config will be created, with the new player's
|
||||
name as it's session id. This way, we can see every player's result !
|
||||
|
||||
So, earlier, we created a MetaConfig, and set the storage on sqlite3, so our data will
|
||||
not be deleted after the quiz stops to run.
|
||||
|
||||
Let's run the script:
|
||||
|
||||
| Who are you? (a student | a teacher): a student
|
||||
| Enter a name: my name
|
||||
| Question 1: what does the cat say?
|
||||
| woof | meow
|
||||
| Your answer: meow
|
||||
| Correct answer!
|
||||
|
|
||||
| Question 2: what do you get by mixing blue and yellow?
|
||||
| green | red | purple
|
||||
| Your answer: green
|
||||
| Correct answer!
|
||||
|
|
||||
| Question 3: where is Bryan?
|
||||
| at school | in his bedroom | in the kitchen
|
||||
| Your answer: at school
|
||||
| Wrong answer... the correct answer was: in the kitchen
|
||||
|
|
||||
| Question 4: which one has 4 legs and 2 wings?
|
||||
| a wyvern | a dragon | a wyrm | a drake
|
||||
| Your answer: a dragon
|
||||
| Correct answer!
|
||||
|
|
||||
| Question 5: why life?
|
||||
| because | I don't know | good question
|
||||
| Your answer: good question
|
||||
| Correct answer!
|
||||
|
|
||||
| Correct answers: 4 out of 5
|
||||
|
||||
When the quiz runs, we will create a new Config in our MetaConfig:
|
||||
|
||||
.. literalinclude:: src/quiz.py
|
||||
:lines: 70-75
|
||||
:linenos:
|
||||
:emphasize-lines: 3
|
||||
|
||||
All results are store in this config (so in the database). So we need to reload those previous config:
|
||||
|
||||
.. literalinclude:: src/quiz.py
|
||||
:lines: 120-124
|
||||
:linenos:
|
||||
:emphasize-lines: 4
|
||||
|
||||
Later, a teacher ca display all those score:
|
||||
|
||||
| Who are you? (a student | a teacher): a teacher
|
||||
| ==================== my name ==========================
|
||||
| Question 1: correct answer
|
||||
| Question 2: correct answer
|
||||
| Question 3: wrong answer: at school
|
||||
| Question 4: correct answer
|
||||
| Question 5: correct answer
|
||||
| my name's score: 4 out of 5
|
||||
|
||||
You've got everything now, so it's your turn to create your own questions and play with
|
||||
your friends !
|
81
docs/requirements.txt
Normal file
|
@ -0,0 +1,81 @@
|
|||
alabaster==0.7.13
|
||||
asttokens==2.4.1
|
||||
attrs==23.1.0
|
||||
Babel==2.13.1
|
||||
certifi==2023.7.22
|
||||
charset-normalizer==3.3.2
|
||||
click==8.1.7
|
||||
colorama==0.4.6
|
||||
comm==0.2.0
|
||||
debugpy==1.8.0
|
||||
decorator==5.1.1
|
||||
docutils==0.18.1
|
||||
exceptiongroup==1.1.3
|
||||
executing==2.0.1
|
||||
fastjsonschema==2.18.1
|
||||
greenlet==3.0.1
|
||||
idna==3.4
|
||||
imagesize==1.4.1
|
||||
importlib-metadata==6.8.0
|
||||
ipykernel==6.26.0
|
||||
ipython==8.17.2
|
||||
jedi==0.19.1
|
||||
Jinja2==3.1.2
|
||||
jsonschema==4.19.2
|
||||
jsonschema-specifications==2023.7.1
|
||||
jupyter-cache==1.0.0
|
||||
jupyter_client==8.6.0
|
||||
jupyter_core==5.5.0
|
||||
livereload==2.6.3
|
||||
markdown-it-py==3.0.0
|
||||
MarkupSafe==2.1.3
|
||||
matplotlib-inline==0.1.6
|
||||
mdit-py-plugins==0.4.0
|
||||
mdurl==0.1.2
|
||||
myst-nb==1.0.0
|
||||
myst-parser==2.0.0
|
||||
nbclient==0.9.0
|
||||
nbformat==5.9.2
|
||||
nest-asyncio==1.5.8
|
||||
packaging==23.2
|
||||
parso==0.8.3
|
||||
pexpect==4.8.0
|
||||
platformdirs==4.0.0
|
||||
prompt-toolkit==3.0.40
|
||||
psutil==5.9.6
|
||||
ptyprocess==0.7.0
|
||||
pure-eval==0.2.2
|
||||
Pygments==2.16.1
|
||||
python-dateutil==2.8.2
|
||||
PyYAML==6.0.1
|
||||
pyzmq==25.1.1
|
||||
referencing==0.30.2
|
||||
requests==2.31.0
|
||||
rpds-py==0.12.0
|
||||
six==1.16.0
|
||||
snowballstemmer==2.2.0
|
||||
Sphinx==7.2.6
|
||||
sphinx-autobuild==2021.3.14
|
||||
sphinx-copybutton==0.5.2
|
||||
sphinx-lesson==0.8.15
|
||||
sphinx-minipres==0.2.1
|
||||
sphinx-rtd-theme==1.3.0
|
||||
sphinx-rtd-theme-ext-color-contrast==0.3.1
|
||||
sphinx-tabs==3.4.4
|
||||
sphinx-togglebutton==0.3.2
|
||||
sphinxcontrib-applehelp==1.0.7
|
||||
sphinxcontrib-devhelp==1.0.5
|
||||
sphinxcontrib-htmlhelp==2.0.4
|
||||
sphinxcontrib-jquery==4.1
|
||||
sphinxcontrib-jsmath==1.0.1
|
||||
sphinxcontrib-qthelp==1.0.6
|
||||
sphinxcontrib-serializinghtml==1.1.9
|
||||
SQLAlchemy==2.0.23
|
||||
stack-data==0.6.3
|
||||
tabulate==0.9.0
|
||||
tornado==6.3.3
|
||||
traitlets==5.13.0
|
||||
typing_extensions==4.8.0
|
||||
urllib3==2.0.7
|
||||
wcwidth==0.2.9
|
||||
zipp==3.17.0
|
18
docs/src/Makefile
Normal file
|
@ -0,0 +1,18 @@
|
|||
SRC=$(wildcard *.py)
|
||||
HTMLFRAGMENT=$(addsuffix .html, $(basename $(SRC)))
|
||||
|
||||
.SUFFIXES:
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: html
|
||||
# make -C ./build all
|
||||
|
||||
html: $(HTMLFRAGMENT)
|
||||
|
||||
%.html: %.py
|
||||
pygmentize -f html -O full,bg=white,style=bw, -o ../en/_build/html/_modules/$@ $<
|
||||
|
||||
clean:
|
||||
# make -C ./build clean
|
||||
|
28
docs/src/api_global_permissive.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
from os.path import exists
|
||||
from tiramisu import FilenameOption, BoolOption, OptionDescription, Leadership, \
|
||||
Config, Calculation, Params, ParamOption, ParamValue, calc_value
|
||||
from tiramisu.error import PropertiesOptionError, ConfigError
|
||||
|
||||
|
||||
def inverse(exists_):
|
||||
return not exists_
|
||||
|
||||
|
||||
filename = FilenameOption('filename',
|
||||
'Filename',
|
||||
multi=True,
|
||||
properties=('mandatory',))
|
||||
exists_ = BoolOption('exists',
|
||||
'This file exists',
|
||||
Calculation(exists, Params(ParamOption(filename))),
|
||||
multi=True,
|
||||
properties=('frozen', 'force_default_on_freeze', 'advanced'))
|
||||
create = BoolOption('create',
|
||||
'Create automaticly the file',
|
||||
multi=True,
|
||||
default_multi=Calculation(inverse, Params(ParamOption(exists_))))
|
||||
new = Leadership('new',
|
||||
'Add new file',
|
||||
[filename, exists_, create])
|
||||
root = OptionDescription('root', 'root', [new])
|
||||
config = Config(root)
|
20
docs/src/api_global_property.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
from os.path import exists
|
||||
from tiramisu import FilenameOption, BoolOption, OptionDescription, Leadership, \
|
||||
Config, Calculation, Params, ParamOption
|
||||
from tiramisu.error import PropertiesOptionError
|
||||
|
||||
|
||||
filename = FilenameOption('filename',
|
||||
'Filename',
|
||||
multi=True,
|
||||
properties=('mandatory',))
|
||||
exists_ = BoolOption('exists',
|
||||
'This file exists',
|
||||
Calculation(exists, Params(ParamOption(filename))),
|
||||
multi=True,
|
||||
properties=('frozen', 'force_default_on_freeze', 'advanced'))
|
||||
new = Leadership('new',
|
||||
'Add new file',
|
||||
[filename, exists_])
|
||||
root = OptionDescription('root', 'root', [new])
|
||||
config = Config(root)
|
147
docs/src/api_option_property.py
Normal file
|
@ -0,0 +1,147 @@
|
|||
from stat import S_IMODE, S_ISDIR, S_ISSOCK
|
||||
from os import lstat, getuid, getgid
|
||||
from os.path import exists
|
||||
from pwd import getpwuid
|
||||
from grp import getgrgid
|
||||
from tiramisu import FilenameOption, UsernameOption, GroupnameOption, IntOption, BoolOption, ChoiceOption, \
|
||||
OptionDescription, Leadership, Config, Calculation, Params, ParamSelfOption, ParamOption, ParamValue, \
|
||||
calc_value
|
||||
from tiramisu.error import LeadershipError, PropertiesOptionError
|
||||
|
||||
|
||||
def get_username(filename, exists, create=False):
|
||||
if exists:
|
||||
uid = lstat(filename).st_uid
|
||||
elif create:
|
||||
# the current uid
|
||||
uid = getuid()
|
||||
else:
|
||||
return
|
||||
|
||||
return getpwuid(uid).pw_name
|
||||
|
||||
|
||||
def get_grpname(filename, exists, create=False):
|
||||
if exists:
|
||||
gid = lstat(filename).st_gid
|
||||
elif create:
|
||||
# the current gid
|
||||
gid = getgid()
|
||||
else:
|
||||
return
|
||||
return getgrgid(gid).gr_name
|
||||
|
||||
|
||||
def calc_type(filename, is_exists):
|
||||
if is_exists:
|
||||
mode = lstat(filename).st_mode
|
||||
if S_ISSOCK(mode):
|
||||
return 'socket'
|
||||
elif S_ISDIR(mode):
|
||||
return 'directory'
|
||||
return 'file'
|
||||
|
||||
|
||||
def calc_mode(filename, is_exists, type):
|
||||
if is_exists:
|
||||
return int(oct(S_IMODE(lstat(filename).st_mode))[2:])
|
||||
if type == 'file':
|
||||
return 644
|
||||
elif type == 'directory':
|
||||
return 755
|
||||
elif type == 'socket':
|
||||
return 444
|
||||
|
||||
|
||||
filename = FilenameOption('filename',
|
||||
'Filename',
|
||||
multi=True,
|
||||
properties=('mandatory',))
|
||||
exists_ = BoolOption('exists',
|
||||
'This file exists',
|
||||
Calculation(exists, Params(ParamOption(filename))),
|
||||
multi=True,
|
||||
properties=('mandatory', 'frozen', 'force_default_on_freeze', 'advanced'))
|
||||
create = BoolOption('create',
|
||||
'Create automaticly the file',
|
||||
multi=True,
|
||||
default_multi=True,
|
||||
properties=(Calculation(calc_value,
|
||||
Params(ParamValue('disabled'),
|
||||
kwargs={'condition': ParamOption(exists_),
|
||||
'expected': ParamValue(True)})),))
|
||||
type_ = ChoiceOption('type',
|
||||
'The file type',
|
||||
('file', 'directory', 'socket'),
|
||||
Calculation(calc_type, Params((ParamOption(filename),
|
||||
ParamOption(exists_)))),
|
||||
multi=True,
|
||||
properties=('force_default_on_freeze', 'mandatory',
|
||||
Calculation(calc_value,
|
||||
Params(ParamValue('hidden'),
|
||||
kwargs={'condition': ParamOption(exists_),
|
||||
'expected': ParamValue(True)})),
|
||||
Calculation(calc_value,
|
||||
Params(ParamValue('frozen'),
|
||||
kwargs={'condition': ParamOption(exists_),
|
||||
'expected': ParamValue(True)}))))
|
||||
username = UsernameOption('user',
|
||||
'User',
|
||||
default_multi=Calculation(get_username, Params((ParamOption(filename),
|
||||
ParamOption(exists_),
|
||||
ParamOption(create, notraisepropertyerror=True)))),
|
||||
multi=True,
|
||||
properties=('force_store_value',
|
||||
Calculation(calc_value,
|
||||
Params(ParamValue('mandatory'),
|
||||
kwargs={'condition': ParamOption(create, notraisepropertyerror=True),
|
||||
'expected': ParamValue(True),
|
||||
'no_condition_is_invalid': ParamValue(True)})),))
|
||||
grpname = GroupnameOption('group',
|
||||
'Group',
|
||||
default_multi=Calculation(get_grpname, Params((ParamOption(filename),
|
||||
ParamOption(exists_),
|
||||
ParamOption(create, notraisepropertyerror=True)))),
|
||||
multi=True,
|
||||
properties=('force_store_value',
|
||||
Calculation(calc_value,
|
||||
Params(ParamValue('mandatory'),
|
||||
kwargs={'condition': ParamOption(create, notraisepropertyerror=True),
|
||||
'expected': ParamValue(True),
|
||||
'no_condition_is_invalid': ParamValue(True)})),))
|
||||
mode = IntOption('mode',
|
||||
'Mode',
|
||||
default_multi=Calculation(calc_mode, Params((ParamOption(filename), ParamOption(exists_), ParamOption(type_)))),
|
||||
multi=True,
|
||||
properties=('mandatory', 'advanced', 'force_store_value'))
|
||||
|
||||
new = Leadership('new',
|
||||
'Add new file',
|
||||
[filename, exists_, create, type_, username, grpname, mode])
|
||||
|
||||
root = OptionDescription('root', 'root', [new])
|
||||
|
||||
config = Config(root)
|
||||
|
||||
|
||||
|
||||
|
||||
#config.option('new.create', 1).value.set(False)
|
||||
#config.option('new.type', 1).value.set('file')
|
||||
#config.option('new.type', 2).value.set('file')
|
||||
#print(config.value.dict())
|
||||
#config.option('new.type', 2).value.set('directory')
|
||||
#print(config.value.dict())
|
||||
#print(config.unrestraint.option('new.mode', 0).owner.isdefault())
|
||||
#print(config.unrestraint.option('new.mode', 1).owner.isdefault())
|
||||
#print(config.unrestraint.option('new.mode', 2).owner.isdefault())
|
||||
#config.property.read_only()
|
||||
#print(config.option('new.mode', 0).owner.isdefault())
|
||||
#print(config.option('new.mode', 1).owner.isdefault())
|
||||
#print(config.option('new.mode', 2).owner.isdefault())
|
||||
#print(config.value.dict())
|
||||
#config.property.read_write()
|
||||
#config.option('new.type', 2).value.set('file')
|
||||
#print(config.value.dict())
|
||||
#config.option('new.mode', 2).value.reset()
|
||||
#print(config.value.dict())
|
31
docs/src/api_value.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
from shutil import disk_usage
|
||||
from os.path import isdir
|
||||
from tiramisu import FilenameOption, FloatOption, OptionDescription, Config, \
|
||||
Calculation, Params, ParamValue, ParamOption, ParamSelfOption
|
||||
|
||||
def valid_is_dir(path):
|
||||
# verify if path is a directory
|
||||
if not isdir(path):
|
||||
raise ValueError('this directory does not exist')
|
||||
|
||||
def calc_disk_usage(path, size='bytes'):
|
||||
# do not calc if path is None
|
||||
if path is None:
|
||||
return None
|
||||
|
||||
if size == 'bytes':
|
||||
div = 1
|
||||
else:
|
||||
# bytes to gigabytes
|
||||
div = 1024 * 1024 * 1024
|
||||
return disk_usage(path).free / div
|
||||
|
||||
|
||||
filename = FilenameOption('path', 'Path', validators=[Calculation(valid_is_dir,
|
||||
Params(ParamSelfOption()))])
|
||||
usage = FloatOption('usage', 'Disk usage', Calculation(calc_disk_usage,
|
||||
Params(ParamOption(filename))))
|
||||
disk = OptionDescription('disk', 'Verify disk usage', [filename, usage])
|
||||
root = OptionDescription('root', 'root', [disk])
|
||||
config = Config(root)
|
||||
config.property.read_write()
|
33
docs/src/api_value_choice.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
from shutil import disk_usage
|
||||
from os.path import isdir
|
||||
from tiramisu import FilenameOption, FloatOption, ChoiceOption, OptionDescription, Config, \
|
||||
Calculation, Params, ParamValue, ParamOption, ParamSelfOption
|
||||
|
||||
def valid_is_dir(path):
|
||||
# verify if path is a directory
|
||||
if not isdir(path):
|
||||
raise ValueError('this directory does not exist')
|
||||
|
||||
def calc_disk_usage(path, size='bytes'):
|
||||
# do not calc if path is None
|
||||
if path is None:
|
||||
return None
|
||||
|
||||
if size == 'bytes':
|
||||
div = 1
|
||||
else:
|
||||
# bytes to gigabytes
|
||||
div = 1024 * 1024 * 1024
|
||||
return disk_usage(path).free / div
|
||||
|
||||
|
||||
filename = FilenameOption('path', 'Path', validators=[Calculation(valid_is_dir,
|
||||
Params(ParamSelfOption()))])
|
||||
size_type = ChoiceOption('size_type', 'Size type', ('bytes', 'giga bytes'), 'bytes')
|
||||
usage = FloatOption('usage', 'Disk usage', Calculation(calc_disk_usage,
|
||||
Params((ParamOption(filename),
|
||||
ParamOption(size_type)))))
|
||||
disk = OptionDescription('disk', 'Verify disk usage', [filename, size_type, usage])
|
||||
root = OptionDescription('root', 'root', [disk])
|
||||
config = Config(root)
|
||||
config.property.read_write()
|
34
docs/src/api_value_leader.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
from shutil import disk_usage
|
||||
from os.path import isdir
|
||||
from tiramisu import FilenameOption, FloatOption, ChoiceOption, OptionDescription, Leadership, \
|
||||
Config, \
|
||||
Calculation, Params, ParamValue, ParamOption, ParamSelfOption
|
||||
|
||||
def valid_is_dir(path):
|
||||
# verify if path is a directory
|
||||
if not isdir(path):
|
||||
raise ValueError('this directory does not exist')
|
||||
|
||||
def calc_disk_usage(path, size):
|
||||
if size == 'bytes':
|
||||
div = 1
|
||||
else:
|
||||
# bytes to gigabytes
|
||||
div = 1024 * 1024 * 1024
|
||||
return disk_usage(path).free / div
|
||||
|
||||
|
||||
filename = FilenameOption('path', 'Path', validators=[Calculation(valid_is_dir,
|
||||
Params(ParamSelfOption(whole=False)))],
|
||||
multi=True)
|
||||
size_type = ChoiceOption('size_type', 'Size type', ('bytes', 'giga bytes'),
|
||||
default_multi='bytes', multi=True)
|
||||
usage = FloatOption('usage', 'Disk usage', Calculation(calc_disk_usage,
|
||||
Params((ParamOption(filename),
|
||||
ParamOption(size_type)))),
|
||||
multi=True)
|
||||
disk = Leadership('disk', 'Verify disk usage', [filename, size_type, usage])
|
||||
root = OptionDescription('root', 'root', [disk])
|
||||
config = Config(root)
|
||||
config.property.read_write()
|
||||
|
34
docs/src/api_value_multi.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
from shutil import disk_usage
|
||||
from os.path import isdir
|
||||
from tiramisu import FilenameOption, FloatOption, ChoiceOption, OptionDescription, Config, \
|
||||
Calculation, Params, ParamValue, ParamOption, ParamSelfOption
|
||||
|
||||
def valid_is_dir(path):
|
||||
# verify if path is a directory
|
||||
if not isdir(path):
|
||||
raise ValueError('this directory does not exist')
|
||||
|
||||
def calc_disk_usage(paths, size='bytes'):
|
||||
if size == 'bytes':
|
||||
div = 1
|
||||
else:
|
||||
# bytes to gigabytes
|
||||
div = 1024 * 1024 * 1024
|
||||
ret = []
|
||||
for path in paths:
|
||||
ret.append(disk_usage(path).free / div)
|
||||
return ret
|
||||
|
||||
|
||||
filename = FilenameOption('path', 'Path', validators=[Calculation(valid_is_dir,
|
||||
Params(ParamSelfOption(whole=False)))],
|
||||
multi=True)
|
||||
size_type = ChoiceOption('size_type', 'Size type', ('bytes', 'giga bytes'), 'bytes')
|
||||
usage = FloatOption('usage', 'Disk usage', Calculation(calc_disk_usage,
|
||||
Params((ParamOption(filename),
|
||||
ParamOption(size_type)))),
|
||||
multi=True)
|
||||
disk = OptionDescription('disk', 'Verify disk usage', [filename, size_type, usage])
|
||||
root = OptionDescription('root', 'root', [disk])
|
||||
config = Config(root)
|
||||
config.property.read_write()
|
232
docs/src/application.py
Normal file
|
@ -0,0 +1,232 @@
|
|||
from tiramisu import BoolOption, ChoiceOption, DomainnameOption, PortOption, URLOption, \
|
||||
OptionDescription, Calculation, Params, ParamOption, ParamValue, \
|
||||
Config, calc_value, calc_value_property_help
|
||||
|
||||
|
||||
def protocols_settings(use: bool, value):
|
||||
if use is True:
|
||||
return value
|
||||
|
||||
|
||||
# this option's value will determine which of the others options are frozen and which are not thanks
|
||||
proxy_mode = ChoiceOption('proxy_mode',
|
||||
'Proxy\'s config mode',
|
||||
('No proxy',
|
||||
'Auto-detect proxy settings for this network',
|
||||
'Use system proxy settings',
|
||||
'Manual proxy configuration',
|
||||
'Automatic proxy configuration URL'),
|
||||
default = 'No proxy',
|
||||
properties=('mandatory',))
|
||||
|
||||
|
||||
http_address = DomainnameOption('http_address',
|
||||
'Address',
|
||||
allow_ip=True,
|
||||
properties=('mandatory',))
|
||||
http_port = PortOption('http_port',
|
||||
'Port',
|
||||
default='8080',
|
||||
properties=('mandatory',))
|
||||
http_proxy = OptionDescription('http_proxy',
|
||||
'HTTP Proxy',
|
||||
[http_address, http_port])
|
||||
|
||||
use_for_all_protocols = BoolOption('use_for_all_protocols',
|
||||
'Use HTTP IP and Port for all protocols',
|
||||
default=True)
|
||||
|
||||
|
||||
# if this option is valued with 'True', set all the others IP and port values to the same as HTTP IP and port.
|
||||
ssl_address = DomainnameOption('ssl_address',
|
||||
'Address',
|
||||
Calculation(protocols_settings,
|
||||
Params((ParamOption(use_for_all_protocols), ParamOption(http_address)))),
|
||||
allow_ip=True,
|
||||
properties=('mandatory', 'force_default_on_freeze',
|
||||
Calculation(calc_value,
|
||||
Params(ParamValue('frozen'),
|
||||
kwargs={'condition': ParamOption(use_for_all_protocols, todict=True),
|
||||
'expected': ParamValue(True)}),
|
||||
calc_value_property_help)))
|
||||
ssl_port = PortOption('ssl_port',
|
||||
'Port',
|
||||
Calculation(protocols_settings,
|
||||
Params((ParamOption(use_for_all_protocols), ParamOption(http_port)))),
|
||||
properties=('mandatory', 'force_default_on_freeze',
|
||||
Calculation(calc_value,
|
||||
Params(ParamValue('frozen'),
|
||||
kwargs={'condition': ParamOption(use_for_all_protocols, todict=True),
|
||||
'expected': ParamValue(True)}),
|
||||
calc_value_property_help)))
|
||||
ssl_proxy = OptionDescription('ssl_proxy',
|
||||
'SSL Proxy',
|
||||
[ssl_address, ssl_port],
|
||||
properties=(Calculation(calc_value,
|
||||
Params(ParamValue('hidden'),
|
||||
kwargs={'condition': ParamOption(use_for_all_protocols, todict=True),
|
||||
'expected': ParamValue(True)}),
|
||||
calc_value_property_help),))
|
||||
|
||||
ftp_address = DomainnameOption('ftp_address',
|
||||
'Address',
|
||||
Calculation(protocols_settings,
|
||||
Params((ParamOption(use_for_all_protocols), ParamOption(http_address)))),
|
||||
allow_ip=True,
|
||||
properties=('mandatory', 'force_default_on_freeze',
|
||||
Calculation(calc_value,
|
||||
Params(ParamValue('frozen'),
|
||||
kwargs={'condition': ParamOption(use_for_all_protocols, todict=True),
|
||||
'expected': ParamValue(True)}),
|
||||
calc_value_property_help)))
|
||||
ftp_port = PortOption('ftp_port',
|
||||
'Port',
|
||||
Calculation(protocols_settings,
|
||||
Params((ParamOption(use_for_all_protocols), ParamOption(http_port)))),
|
||||
properties=('force_default_on_freeze',
|
||||
Calculation(calc_value,
|
||||
Params(ParamValue('frozen'),
|
||||
kwargs={'condition': ParamOption(use_for_all_protocols, todict=True),
|
||||
'expected': ParamValue(True)}),
|
||||
calc_value_property_help)))
|
||||
ftp_proxy = OptionDescription('ftp_proxy',
|
||||
'FTP Proxy',
|
||||
[ftp_address, ftp_port],
|
||||
properties=(Calculation(calc_value,
|
||||
Params(ParamValue('hidden'),
|
||||
kwargs={'condition': ParamOption(use_for_all_protocols, todict=True),
|
||||
'expected': ParamValue(True)}),
|
||||
calc_value_property_help),))
|
||||
|
||||
socks_address = DomainnameOption('socks_address',
|
||||
'Address',
|
||||
Calculation(protocols_settings,
|
||||
Params((ParamOption(use_for_all_protocols), ParamOption(http_address)))),
|
||||
allow_ip=True,
|
||||
properties=('mandatory', 'force_default_on_freeze',
|
||||
Calculation(calc_value,
|
||||
Params(ParamValue('frozen'),
|
||||
kwargs={'condition': ParamOption(use_for_all_protocols, todict=True),
|
||||
'expected': ParamValue(True)}),
|
||||
calc_value_property_help)))
|
||||
socks_port = PortOption('socks_port',
|
||||
'Port',
|
||||
Calculation(protocols_settings,
|
||||
Params((ParamOption(use_for_all_protocols), ParamOption(http_port)))),
|
||||
properties=('mandatory', 'force_default_on_freeze',
|
||||
Calculation(calc_value,
|
||||
Params(ParamValue('frozen'),
|
||||
kwargs={'condition': ParamOption(use_for_all_protocols, todict=True),
|
||||
'expected': ParamValue(True)}),
|
||||
calc_value_property_help)))
|
||||
socks_version = ChoiceOption('socks_version',
|
||||
'SOCKS host version used by proxy',
|
||||
('v4', 'v5'),
|
||||
default='v5',
|
||||
properties=('force_default_on_freeze',
|
||||
Calculation(calc_value,
|
||||
Params(ParamValue('frozen'),
|
||||
kwargs={'condition': ParamOption(use_for_all_protocols, todict=True),
|
||||
'expected': ParamValue(True)}),
|
||||
calc_value_property_help)))
|
||||
socks_proxy = OptionDescription('socks_proxy',
|
||||
'Socks host proxy',
|
||||
[socks_address, socks_port, socks_version],
|
||||
properties=(Calculation(calc_value,
|
||||
Params(ParamValue('hidden'),
|
||||
kwargs={'condition': ParamOption(use_for_all_protocols, todict=True),
|
||||
'expected': ParamValue(True)}),
|
||||
calc_value_property_help),))
|
||||
protocols = OptionDescription('protocols',
|
||||
'Protocols parameters',
|
||||
[http_proxy,
|
||||
use_for_all_protocols,
|
||||
ssl_proxy,
|
||||
ftp_proxy,
|
||||
socks_proxy],
|
||||
properties=(Calculation(calc_value,
|
||||
Params(ParamValue('disabled'),
|
||||
kwargs={'condition': ParamOption(proxy_mode, todict=True),
|
||||
'expected': ParamValue('Manual proxy configuration'),
|
||||
'reverse_condition': ParamValue(True)}),
|
||||
calc_value_property_help),))
|
||||
|
||||
auto_config_url = URLOption('auto_config_url',
|
||||
'Proxy\'s auto config URL',
|
||||
allow_ip=True,
|
||||
properties=('mandatory',
|
||||
Calculation(calc_value,
|
||||
Params(ParamValue('disabled'),
|
||||
kwargs={'condition': ParamOption(proxy_mode, todict=True),
|
||||
'expected': ParamValue('Automatic proxy configuration URL'),
|
||||
'reverse_condition': ParamValue(True)}),
|
||||
calc_value_property_help),))
|
||||
|
||||
no_proxy = DomainnameOption('no_proxy',
|
||||
'Address for which proxy will be desactivated',
|
||||
multi=True,
|
||||
allow_ip=True,
|
||||
allow_cidr_network=True,
|
||||
allow_without_dot=True,
|
||||
allow_startswith_dot=True,
|
||||
properties=(Calculation(calc_value,
|
||||
Params(ParamValue('disabled'),
|
||||
kwargs={'condition': ParamOption(proxy_mode, todict=True),
|
||||
'expected': ParamValue('No proxy')}),
|
||||
calc_value_property_help),))
|
||||
|
||||
prompt_authentication = BoolOption('prompt_authentication',
|
||||
'Prompt for authentication if password is saved',
|
||||
default=False,
|
||||
properties=(Calculation(calc_value,
|
||||
Params(ParamValue('disabled'),
|
||||
kwargs={'condition': ParamOption(proxy_mode, todict=True),
|
||||
'expected': ParamValue('No proxy')}),
|
||||
calc_value_property_help),))
|
||||
proxy_dns_socks5 = BoolOption('proxy_dns_socks5',
|
||||
'Use Proxy DNS when using SOCKS v5',
|
||||
default=False,
|
||||
properties=(Calculation(calc_value,
|
||||
Params(ParamValue('disabled'),
|
||||
kwargs={'condition_1': ParamOption(socks_version,
|
||||
raisepropertyerror=True),
|
||||
'expected_1': ParamValue('v4'),
|
||||
'condition_2': ParamOption(proxy_mode, todict=True),
|
||||
'expected_2': ParamValue('No proxy'),
|
||||
'condition_operator': ParamValue('OR')}),
|
||||
calc_value_property_help),))
|
||||
enable_dns_over_https = BoolOption('enable_dns_over_https',
|
||||
'Enable DNS over HTTPS',
|
||||
default=False)
|
||||
|
||||
used_dns = ChoiceOption('used_dns',
|
||||
'Used DNS',
|
||||
('default', 'custom'),
|
||||
properties=(Calculation(calc_value,
|
||||
Params(ParamValue('disabled'),
|
||||
kwargs={'condition': ParamOption(enable_dns_over_https, todict=True),
|
||||
'expected': ParamValue(False)}),
|
||||
calc_value_property_help),))
|
||||
|
||||
custom_dns_url = URLOption('custom_dns_url',
|
||||
'Custom DNS URL',
|
||||
properties=(Calculation(calc_value,
|
||||
Params(ParamValue('disabled'),
|
||||
kwargs={'condition': ParamOption(used_dns, todict=True,
|
||||
raisepropertyerror=True),
|
||||
'expected': ParamValue('default')}),
|
||||
calc_value_property_help),))
|
||||
dns_over_https = OptionDescription('dns_over_https',
|
||||
'DNS over HTTPS',
|
||||
[enable_dns_over_https, used_dns, custom_dns_url])
|
||||
|
||||
rootod = OptionDescription('proxy',
|
||||
'Proxy parameters',
|
||||
[proxy_mode,
|
||||
protocols,
|
||||
no_proxy,
|
||||
auto_config_url,
|
||||
prompt_authentication,
|
||||
proxy_dns_socks5, dns_over_https])
|
||||
proxy_config = Config(rootod)
|
||||
proxy_config.property.read_write()
|
73
docs/src/calculation.py
Normal file
|
@ -0,0 +1,73 @@
|
|||
from tiramisu import Config, OptionDescription, Leadership, IntOption, Params, ParamOption, ParamValue, ParamContext, ParamIndex
|
||||
|
||||
def a_function():
|
||||
pass
|
||||
Calculation(a_function)
|
||||
|
||||
|
||||
def a_function_with_parameters(value1, value2):
|
||||
return value1 + ' ' + value2
|
||||
Calculation(a_function_with_parameters, Params(ParamValue('my value 1'), kwargs={value2: ParamValue('my value 2')}))
|
||||
|
||||
|
||||
def a_function_with_parameters(value1, value2):
|
||||
return value1 + ' ' + value2
|
||||
Calculation(a_function_with_parameters, Params((ParamValue('my value 1'), ParamValue('my value 2'))))
|
||||
|
||||
|
||||
def a_function_with_option(option1):
|
||||
return option1
|
||||
option1 = IntOption('option1', 'first option', 1)
|
||||
Calculation(a_function_with_option, Params(ParamOption(option1)))
|
||||
|
||||
|
||||
def a_function_with_option(option1):
|
||||
return option1
|
||||
option1 = IntOption('option1', 'first option', 1, properties=('disabled',))
|
||||
Calculation(a_function_with_option, Params(ParamOption(option1)))
|
||||
|
||||
def a_function_with_option(option1):
|
||||
return option1
|
||||
Calculation(a_function_with_option, Params(ParamOption(option1, raisepropertyerror=True)))
|
||||
|
||||
def a_function_with_option(option1=None):
|
||||
return option1
|
||||
Calculation(a_function_with_option, Params(ParamOption(option1, notraisepropertyerror=True)))
|
||||
|
||||
def a_function_with_dict_option(option1):
|
||||
return "the option {} has value {}".format(option1['name'], option1['value'])
|
||||
Calculation(a_function_with_option, Params(ParamOption(todict=True)))
|
||||
|
||||
|
||||
def a_function_with_context(context):
|
||||
pass
|
||||
Calculation(a_function_with_context, Params(ParamContext()))
|
||||
|
||||
def a_function_multi(option1):
|
||||
return option1
|
||||
option1 = IntOption('option1', 'option1', [1], multi=True)
|
||||
Calculation(a_function, Params(ParamOption(option1)))
|
||||
|
||||
def a_function_leader(option):
|
||||
return option
|
||||
leader = IntOption('leader', 'leader', [1], multi=True)
|
||||
follower1 = IntOption('follower1', 'follower1', default_multi=2, multi=True)
|
||||
follower2 = IntOption('follower2', 'follower2', default_multi=3, multi=True)
|
||||
leadership = Leadership('leadership', 'leadership', [leader, follower1, follower2])
|
||||
Calculation(a_function_leader, Params(ParamOption(leader)))
|
||||
|
||||
def a_function_follower(follower):
|
||||
return follower
|
||||
leader = IntOption('leader', 'leader', [1], multi=True)
|
||||
follower1 = IntOption('follower1', 'follower1', default_multi=2, multi=True)
|
||||
follower2 = IntOption('follower2', 'follower2', default_multi=3, multi=True)
|
||||
leadership = Leadership('leadership', 'leadership', [leader, follower1, follower2])
|
||||
Calculation(a_function_follower, Params(ParamOption(follower1)))
|
||||
|
||||
def a_function_index(index):
|
||||
return index
|
||||
leader = IntOption('leader', 'leader', [1], multi=True)
|
||||
follower1 = IntOption('follower1', 'follower1', default_multi=2, multi=True)
|
||||
follower2 = IntOption('follower2', 'follower2', default_multi=3, multi=True)
|
||||
leadership = Leadership('leadership', 'leadership', [leader, follower1, follower2])
|
||||
Calculation(a_function_index, Params(ParamIndex()))
|
6
docs/src/find.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
"this is only to make sure that the tiramisu library loads properly"
|
||||
|
||||
cfg = Config(rootod)
|
||||
|
||||
|
||||
|
19
docs/src/getting_started.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
"getting started with the tiramisu library (it loads and prints properly)"
|
||||
|
||||
from tiramisu import Config
|
||||
from tiramisu import OptionDescription, BoolOption
|
||||
|
||||
# let's create a group of options
|
||||
descr = OptionDescription("optgroup", "", [
|
||||
# ... with only one option inside
|
||||
BoolOption("bool", "", default=False)
|
||||
])
|
||||
|
||||
cfg = Config(descr)
|
||||
|
||||
# the global help about the config
|
||||
cfg.help()
|
||||
# help about an option
|
||||
cfg.option.help()
|
||||
# the config's __repr__
|
||||
print(cfg)
|
11
docs/src/own_option.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import re
|
||||
from tiramisu import RegexpOption
|
||||
|
||||
|
||||
class VowelOption(RegexpOption):
|
||||
__slots__ = tuple()
|
||||
_type = 'vowel'
|
||||
_display_name = "string with vowel"
|
||||
_regexp = re.compile(r"^[aeiouy]*$")
|
43
docs/src/own_option2.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from tiramisu import Option
|
||||
from tiramisu.error import ValueWarning
|
||||
import warnings
|
||||
|
||||
|
||||
class LipogramOption(Option):
|
||||
__slots__ = tuple()
|
||||
_type = 'lipogram'
|
||||
_display_name = 'lipogram'
|
||||
def __init__(self,
|
||||
*args,
|
||||
min_len=100,
|
||||
**kwargs):
|
||||
# store extra parameters
|
||||
extra = {'_min_len': min_len}
|
||||
super().__init__(*args,
|
||||
extra=extra,
|
||||
**kwargs)
|
||||
|
||||
def validate(self,
|
||||
value):
|
||||
# first, valid that the value is a string
|
||||
if not isinstance(value, str):
|
||||
raise ValueError('invalid string')
|
||||
# and verify that there is any 'e' in the sentense
|
||||
if 'e' in value:
|
||||
raise ValueError('Perec wrote a book without any "e", you could not do it in a simple sentence?')
|
||||
|
||||
def second_level_validation(self,
|
||||
value,
|
||||
warnings_only):
|
||||
# retrive parameter in extra
|
||||
min_len = self.impl_get_extra('_min_len')
|
||||
# verify the sentense length
|
||||
if len(value) < min_len:
|
||||
# raise message, in this case, warning and error message are different
|
||||
if warnings_only:
|
||||
msg = f'it would be better to have at least {min_len} characters in the sentence'
|
||||
else:
|
||||
msg = f'you must have at least {min_len} characters in the sentence'
|
||||
raise ValueError(msg)
|
20
docs/src/property.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
from tiramisu import Config
|
||||
from tiramisu import StrOption, OptionDescription
|
||||
|
||||
# let's declare some options
|
||||
var1 = StrOption('var1', 'first option')
|
||||
# an option with a default value
|
||||
var2 = StrOption('var2', 'second option', u'value')
|
||||
# let's create a group of options
|
||||
od1 = OptionDescription('od1', 'first OD', [var1, var2])
|
||||
|
||||
# let's create another group of options
|
||||
rootod = OptionDescription('rootod', '', [od1])
|
||||
|
||||
# let's create the config
|
||||
cfg = Config(rootod)
|
||||
# the api is read only
|
||||
cfg.property.read_only()
|
||||
# the read_write api is available
|
||||
cfg.property.read_write()
|
||||
|
51
docs/src/proxy.py
Normal file
|
@ -0,0 +1,51 @@
|
|||
from tiramisu import IPOption, PortOption, BoolOption, ChoiceOption, DomainnameOption, \
|
||||
URLOption, NetworkOption, NetmaskOption, \
|
||||
SymLinkOption, OptionDescription, Leadership, Config
|
||||
|
||||
|
||||
proxy_mode = ChoiceOption('proxy_mode', 'Proxy\'s config mode', ('No proxy',
|
||||
'Manual proxy configuration',
|
||||
'Automatic proxy configuration URL'),
|
||||
properties=('positional', 'mandatory'))
|
||||
http_ip_address = IPOption('http_ip_address', 'Proxy\'s HTTP IP', properties=('mandatory',))
|
||||
http_ip_short = SymLinkOption('i', http_ip_address)
|
||||
http_port = PortOption('http_port', 'Proxy\'s HTTP Port', default='8080', properties=('mandatory',))
|
||||
http_port_short = SymLinkOption('p', http_port)
|
||||
manual_proxy = OptionDescription('manual_proxy', 'Manual proxy settings', [http_ip_address, http_ip_short, http_port, http_port_short],
|
||||
requires=[{'option': proxy_mode, 'expected': 'Manual proxy configuration', 'action':'disabled', 'inverse':True}])
|
||||
|
||||
auto_config_url = URLOption('auto_config_url','Proxy\'s auto config URL', properties=('mandatory',))
|
||||
auto_config_url_short = SymLinkOption('i', auto_config_url)
|
||||
automatic_proxy = OptionDescription('automatic_proxy', 'Automatic proxy setting',
|
||||
[auto_config_url, auto_config_url_short],
|
||||
requires=[{'option': proxy_mode, 'expected': 'Automatic proxy configuration URL', 'action':'disabled', 'inverse': True}])
|
||||
|
||||
configuration = OptionDescription('configuration', None,
|
||||
[manual_proxy, automatic_proxy])
|
||||
|
||||
no_proxy_domain = DomainnameOption('no_proxy_domain', 'Domain names for which proxy will be desactivated', multi=True)
|
||||
no_proxy_network = NetworkOption('no_proxy_network', 'Network addresses', multi=True)
|
||||
no_proxy_network_short = SymLinkOption('n', no_proxy_network)
|
||||
no_proxy_netmask = NetmaskOption('no_proxy_netmask', 'Netmask addresses', multi=True, properties=('mandatory',))
|
||||
no_proxy_network_leadership = Leadership('no_proxy_network', 'Network for which proxy will be desactivated', [no_proxy_network, no_proxy_netmask])
|
||||
no_proxy = OptionDescription('no_proxy', 'Disabled proxy',
|
||||
[no_proxy_domain, no_proxy_network_leadership],
|
||||
requires=[{'option': proxy_mode, 'expected': 'No proxy', 'action':'disabled'}, {'option': proxy_mode, 'expected': None, 'action':'disabled'}])
|
||||
|
||||
dns_over_https = BoolOption('dns_over_https', 'Enable DNS over HTTPS', default=False)
|
||||
|
||||
root = OptionDescription('proxy', 'Proxy parameters',
|
||||
[proxy_mode, configuration, no_proxy, dns_over_https])
|
||||
|
||||
def display_name(option, dyn_name):
|
||||
return "--" + option.impl_getpath()
|
||||
|
||||
proxy_config = Config(root, display_name=display_name)
|
||||
proxy_config.property.read_write()
|
||||
|
||||
from tiramisu_cmdline_parser import TiramisuCmdlineParser
|
||||
parser = TiramisuCmdlineParser(proxy_config)
|
||||
parser.parse_args()
|
||||
|
||||
from pprint import pprint
|
||||
pprint(proxy_config.value.dict())
|
54
docs/src/proxy_persistent.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
from tiramisu import IPOption, PortOption, BoolOption, ChoiceOption, DomainnameOption, \
|
||||
URLOption, NetworkOption, NetmaskOption, \
|
||||
SymLinkOption, OptionDescription, Leadership, Config
|
||||
|
||||
|
||||
proxy_mode = ChoiceOption('proxy_mode', 'Proxy\'s config mode', ('No proxy',
|
||||
'Manual proxy configuration',
|
||||
'Automatic proxy configuration URL'),
|
||||
properties=('positional', 'mandatory'))
|
||||
http_ip_address = IPOption('http_ip_address', 'Proxy\'s HTTP IP', properties=('mandatory',))
|
||||
http_ip_short = SymLinkOption('i', http_ip_address)
|
||||
http_port = PortOption('http_port', 'Proxy\'s HTTP Port', default='8080', properties=('mandatory',))
|
||||
http_port_short = SymLinkOption('p', http_port)
|
||||
manual_proxy = OptionDescription('manual_proxy', 'Manual proxy settings', [http_ip_address, http_ip_short, http_port, http_port_short],
|
||||
requires=[{'option': proxy_mode, 'expected': 'Manual proxy configuration', 'action':'disabled', 'inverse':True}])
|
||||
|
||||
auto_config_url = URLOption('auto_config_url','Proxy\'s auto config URL', properties=('mandatory',))
|
||||
auto_config_url_short = SymLinkOption('i', auto_config_url)
|
||||
automatic_proxy = OptionDescription('automatic_proxy', 'Automatic proxy setting',
|
||||
[auto_config_url, auto_config_url_short],
|
||||
requires=[{'option': proxy_mode, 'expected': 'Automatic proxy configuration URL', 'action':'disabled', 'inverse': True}])
|
||||
|
||||
configuration = OptionDescription('configuration', None,
|
||||
[manual_proxy, automatic_proxy])
|
||||
|
||||
no_proxy_domain = DomainnameOption('no_proxy_domain', 'Domain names for which proxy will be desactivated', multi=True)
|
||||
no_proxy_network = NetworkOption('no_proxy_network', 'Network addresses', multi=True)
|
||||
no_proxy_network_short = SymLinkOption('n', no_proxy_network)
|
||||
no_proxy_netmask = NetmaskOption('no_proxy_netmask', 'Netmask addresses', multi=True, properties=('mandatory',))
|
||||
no_proxy_network_leadership = Leadership('no_proxy_network', 'Network for which proxy will be desactivated', [no_proxy_network, no_proxy_netmask])
|
||||
no_proxy = OptionDescription('no_proxy', 'Disabled proxy',
|
||||
[no_proxy_domain, no_proxy_network_leadership],
|
||||
requires=[{'option': proxy_mode, 'expected': 'No proxy', 'action':'disabled'}, {'option': proxy_mode, 'expected': None, 'action':'disabled'}])
|
||||
|
||||
dns_over_https = BoolOption('dns_over_https', 'Enable DNS over HTTPS', default=False)
|
||||
|
||||
root = OptionDescription('proxy', 'Proxy parameters',
|
||||
[proxy_mode, configuration, no_proxy, dns_over_https])
|
||||
|
||||
def display_name(option, dyn_name):
|
||||
return "--" + option.impl_getpath()
|
||||
|
||||
from tiramisu import default_storage
|
||||
default_storage.setting(engine='sqlite3')
|
||||
|
||||
proxy_config = Config(root, display_name=display_name, persistent=True, session_id='proxy')
|
||||
proxy_config.property.read_write()
|
||||
|
||||
from tiramisu_cmdline_parser import TiramisuCmdlineParser
|
||||
parser = TiramisuCmdlineParser(proxy_config)
|
||||
parser.parse_args(valid_mandatory=False)
|
||||
|
||||
from pprint import pprint
|
||||
pprint(proxy_config.value.dict())
|
132
docs/src/quiz.py
Normal file
|
@ -0,0 +1,132 @@
|
|||
from sys import exit
|
||||
from tiramisu import (MetaConfig, Config, OptionDescription,
|
||||
BoolOption, ChoiceOption, StrOption, IntOption,
|
||||
Calculation, Params, ParamOption,
|
||||
default_storage, list_sessions)
|
||||
from tiramisu.error import ConflictError
|
||||
|
||||
|
||||
default_storage.setting(engine="sqlite3")
|
||||
|
||||
|
||||
def verif(q: str, a: str):
|
||||
return q == a
|
||||
|
||||
|
||||
def results(*verif):
|
||||
return sum(verif)
|
||||
|
||||
|
||||
questions = [{'description': 'what does the cat say?',
|
||||
'proposal': ('woof', 'meow'),
|
||||
'answer': 'meow'},
|
||||
{'description': 'what do you get by mixing blue and yellow?',
|
||||
'proposal': ('green', 'red', 'purple'),
|
||||
'answer': 'green'},
|
||||
{'description': 'where is Bryan?',
|
||||
'proposal': ('at school', 'in his bedroom', 'in the kitchen'),
|
||||
'answer': 'in the kitchen'},
|
||||
{'description': 'which one has 4 legs and 2 wings?',
|
||||
'proposal': ('a wyvern', 'a dragon', 'a wyrm', 'a drake'),
|
||||
'answer': 'a dragon'},
|
||||
{'description': 'why life?',
|
||||
'proposal': ('because', 'I don\'t know', 'good question'),
|
||||
'answer': 'good question'},
|
||||
]
|
||||
|
||||
|
||||
options_obj = []
|
||||
results_obj = []
|
||||
for idx, question in enumerate(questions):
|
||||
idx += 1
|
||||
choice = ChoiceOption('question',
|
||||
question['description'],
|
||||
question['proposal'])
|
||||
answer = StrOption('answer',
|
||||
f'Answer {idx}',
|
||||
default=question['answer'],
|
||||
properties=('frozen',))
|
||||
boolean = BoolOption('verif',
|
||||
f'Verif of question {idx}',
|
||||
Calculation(verif,
|
||||
Params((ParamOption(choice),
|
||||
ParamOption(answer)))),
|
||||
properties=('frozen',))
|
||||
optiondescription = OptionDescription(f'question_{idx}',
|
||||
f'Question {idx}',
|
||||
[choice, answer, boolean])
|
||||
options_obj.append(optiondescription)
|
||||
results_obj.append(ParamOption(boolean))
|
||||
|
||||
|
||||
options_obj.append(IntOption('res',
|
||||
'Quiz results',
|
||||
Calculation(results,
|
||||
Params(tuple(results_obj)))))
|
||||
rootod = OptionDescription('root', '', options_obj)
|
||||
meta_cfg = MetaConfig([], optiondescription=rootod, persistent=True, session_id="quiz")
|
||||
|
||||
|
||||
def run_quiz(meta_cfg: MetaConfig):
|
||||
pseudo = input("Enter a name: ")
|
||||
try:
|
||||
cfg = meta_cfg.config.new(pseudo, persistent=True)
|
||||
except ConflictError:
|
||||
print(f'Hey {pseudo} you already answered the questionnaire')
|
||||
exit()
|
||||
cfg.property.read_write()
|
||||
|
||||
for idx, question in enumerate(cfg.option.list(type='optiondescription')):
|
||||
question_id = question.option.doc()
|
||||
question_obj = question.option('question')
|
||||
question_doc = question_obj.option.doc()
|
||||
print(f'{question_id}: {question_doc}')
|
||||
print(*question_obj.value.list(), sep=" | ")
|
||||
while True:
|
||||
input_ans = input('Your answer: ')
|
||||
try:
|
||||
question_obj.value.set(input_ans)
|
||||
except ValueError as err:
|
||||
err.prefix = ''
|
||||
print(err)
|
||||
else:
|
||||
break
|
||||
if question.option('verif').value.get() is True:
|
||||
print('Correct answer!')
|
||||
else:
|
||||
print("Wrong answer... the correct answer was:", question.option('answer').value.get())
|
||||
print('')
|
||||
qno = idx + 1
|
||||
print("Correct answers:", cfg.option('res').value.get(), "out of", qno)
|
||||
if cfg.option('res').value.get() == 0 :
|
||||
print("Ouch... Maybe next time?")
|
||||
elif cfg.option('res').value.get() == qno :
|
||||
print("Wow, great job!")
|
||||
|
||||
|
||||
def quiz_results(meta_cfg: MetaConfig):
|
||||
for cfg in meta_cfg.config.list():
|
||||
print(f"==================== {cfg.config.name()} ==========================")
|
||||
for idx, question in enumerate(cfg.option.list(type='optiondescription')):
|
||||
if question.option('verif').value.get() is True:
|
||||
answer = "correct answer"
|
||||
else:
|
||||
answer = "wrong answer: " + str(question.option('question').value.get())
|
||||
print(question.option.doc() + ': ' + answer)
|
||||
qno = idx + 1
|
||||
print(f'{cfg.config.name()}\'s score: {cfg.option("res").value.get()} out of {qno}')
|
||||
|
||||
|
||||
# reload old sessions
|
||||
for session_id in list_sessions():
|
||||
# our meta config is just here to be a base, so we don't want its session id to be used
|
||||
if session_id != "quiz":
|
||||
meta_cfg.config.new(session_id, persistent=True)
|
||||
while True:
|
||||
who = input("Who are you? (a student | a teacher): ")
|
||||
if who in ['a student', 'a teacher']:
|
||||
break
|
||||
if who == 'a student':
|
||||
run_quiz(meta_cfg)
|
||||
else:
|
||||
quiz_results(meta_cfg)
|
133
docs/src/validator.py
Normal file
|
@ -0,0 +1,133 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from tiramisu import StrOption, IntOption, OptionDescription, Config, \
|
||||
Calculation, Params, ParamOption, ParamSelfOption, ParamValue
|
||||
from tiramisu.error import ValueWarning
|
||||
import warnings
|
||||
from re import match
|
||||
|
||||
|
||||
# Creation differents function
|
||||
def is_password_conform(password):
|
||||
# password must containe at least a number, a lowercase letter, an uppercase letter and a symbol
|
||||
if not match(r'(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*\W)', password):
|
||||
raise ValueError('please choose a stronger password, try a mix of letters, numbers and symbols')
|
||||
|
||||
|
||||
# Password must have at least min_len characters
|
||||
def password_correct_len(min_len, recommand_len, password):
|
||||
if len(password) < min_len:
|
||||
raise ValueError(f'use {min_len} characters or more for your password')
|
||||
# password should have at least recommand_len characters
|
||||
if len(password) < recommand_len:
|
||||
raise ValueWarning(f'it would be better to use more than {recommand_len} characters for your password')
|
||||
|
||||
|
||||
def user_not_in_password(login, password):
|
||||
if login in password:
|
||||
raise ValueError('the login must not be part of the password')
|
||||
|
||||
|
||||
def password_match(password1, password2):
|
||||
if password1 != password2:
|
||||
raise ValueError("those passwords didn't match, try again")
|
||||
|
||||
|
||||
# Create first option to ask user's login
|
||||
login = StrOption('login', 'Login', properties=('mandatory',))
|
||||
|
||||
# Creation calculatin for first password
|
||||
calc1 = Calculation(is_password_conform,
|
||||
Params(ParamSelfOption()))
|
||||
|
||||
calc2 = Calculation(password_correct_len,
|
||||
Params((ParamValue(8),
|
||||
ParamValue(12),
|
||||
ParamSelfOption())))
|
||||
|
||||
calc3 = Calculation(user_not_in_password,
|
||||
Params(kwargs={'login': ParamOption(login),
|
||||
'password': ParamSelfOption()}),
|
||||
warnings_only=True)
|
||||
|
||||
|
||||
# Create second option to ask user's password
|
||||
password1 = StrOption('password1',
|
||||
'Password',
|
||||
properties=('mandatory',),
|
||||
validators=[calc1, calc2, calc3])
|
||||
|
||||
# Create third option to confirm user's password
|
||||
password2 = StrOption('password2',
|
||||
'Confirm',
|
||||
properties=('mandatory',),
|
||||
validators=[Calculation(password_match, Params((ParamOption(password1), ParamSelfOption())))])
|
||||
|
||||
# Creation optiondescription and config
|
||||
od = OptionDescription('password', 'Define your password', [password1, password2])
|
||||
root = OptionDescription('root', '', [login, od])
|
||||
config = Config(root)
|
||||
config.property.read_write()
|
||||
|
||||
# no number and no symbol (with prefix)
|
||||
config.option('login').value.set('user')
|
||||
try:
|
||||
config.option('password.password1').value.set('aAbBc')
|
||||
except ValueError as err:
|
||||
print(f'Error: {err}')
|
||||
|
||||
# no number and no symbol
|
||||
config.option('login').value.set('user')
|
||||
try:
|
||||
config.option('password.password1').value.set('aAbBc')
|
||||
except ValueError as err:
|
||||
err.prefix = ''
|
||||
print(f'Error: {err}')
|
||||
|
||||
# too short password
|
||||
config.option('login').value.set('user')
|
||||
try:
|
||||
config.option('password.password1').value.set('aZ$1')
|
||||
except ValueError as err:
|
||||
err.prefix = ''
|
||||
print(f'Error: {err}')
|
||||
|
||||
# warnings too short password
|
||||
warnings.simplefilter('always', ValueWarning)
|
||||
config.option('login').value.set('user')
|
||||
with warnings.catch_warnings(record=True) as warn:
|
||||
config.option('password.password1').value.set('aZ$1bN:2')
|
||||
if warn:
|
||||
warn[0].message.prefix = ''
|
||||
print(f'Warning: {warn[0].message}')
|
||||
password = config.option('password.password1').value.get()
|
||||
print(f'The password is "{password}"')
|
||||
|
||||
# password with login
|
||||
warnings.simplefilter('always', ValueWarning)
|
||||
config.option('login').value.set('user')
|
||||
with warnings.catch_warnings(record=True) as warn:
|
||||
config.option('password.password1').value.set('aZ$1bN:2u@1Bjuser')
|
||||
if warn:
|
||||
warn[0].message.prefix = ''
|
||||
print(f'Warning: {warn[0].message}')
|
||||
password = config.option('password.password1').value.get()
|
||||
print(f'The password is "{password}"')
|
||||
|
||||
# password1 not matching password2
|
||||
config.option('login').value.set('user')
|
||||
config.option('password.password1').value.set('aZ$1bN:2u@1Bj')
|
||||
try:
|
||||
config.option('password.password2').value.set('aZ$1aaaa')
|
||||
except ValueError as err:
|
||||
err.prefix = ''
|
||||
print(f'Error: {err}')
|
||||
|
||||
# and finaly passwod match
|
||||
config.option('login').value.set('user')
|
||||
config.option('password.password1').value.set('aZ$1bN:2u@1Bj')
|
||||
config.option('password.password2').value.set('aZ$1bN:2u@1Bj')
|
||||
config.property.read_only()
|
||||
user_login = config.option('login').value.get()
|
||||
password = config.option('password.password2').value.get()
|
||||
print(f'The password for "{user_login}" is "{password}"')
|
41
docs/src/validator_follower.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
from tiramisu import StrOption, IntOption, Leadership, OptionDescription, Config, \
|
||||
Calculation, Params, ParamSelfOption, ParamIndex
|
||||
from tiramisu.error import ValueWarning
|
||||
import warnings
|
||||
|
||||
|
||||
def valid_pourcent(option, current_option, index):
|
||||
if None in option:
|
||||
return
|
||||
total = sum(option)
|
||||
if total > 100:
|
||||
raise ValueError(f'the value {current_option} (at index {index}) is too big, the total is {total}%')
|
||||
|
||||
|
||||
calculation = Calculation(valid_pourcent, Params((ParamSelfOption(whole=True),
|
||||
ParamSelfOption(),
|
||||
ParamIndex())))
|
||||
|
||||
|
||||
user = StrOption('user', 'User', multi=True)
|
||||
percent = IntOption('percent',
|
||||
'Distribution',
|
||||
multi=True,
|
||||
validators=[calculation])
|
||||
od = Leadership('percent', 'Percent', [user, percent])
|
||||
config = Config(OptionDescription('root', 'root', [od]))
|
||||
|
||||
|
||||
config.option('percent.user').value.set(['user1', 'user2'])
|
||||
config.option('percent.percent', 0).value.set(20)
|
||||
|
||||
|
||||
# too big
|
||||
try:
|
||||
config.option('percent.percent', 1).value.set(90)
|
||||
except ValueError as err:
|
||||
err.prefix = ''
|
||||
print(f'Error: {err}')
|
||||
|
||||
# correct
|
||||
config.option('percent.percent', 1).value.set(80)
|
44
docs/src/validator_multi.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
from tiramisu import IntOption, OptionDescription, Config, \
|
||||
Calculation, Params, ParamSelfOption
|
||||
from tiramisu.error import ValueWarning
|
||||
import warnings
|
||||
|
||||
|
||||
def valid_pourcent(option):
|
||||
total = sum(option)
|
||||
if total > 100:
|
||||
raise ValueError(f'the total {total}% is bigger than 100%')
|
||||
if total < 100:
|
||||
raise ValueWarning(f'the total {total}% is lower than 100%')
|
||||
|
||||
|
||||
percent = IntOption('percent',
|
||||
'Percent',
|
||||
multi=True,
|
||||
validators=[Calculation(valid_pourcent, Params(ParamSelfOption()))])
|
||||
config = Config(OptionDescription('root', 'root', [percent]))
|
||||
|
||||
|
||||
# too big
|
||||
try:
|
||||
config.option('percent').value.set([20, 90])
|
||||
except ValueError as err:
|
||||
err.prefix = ''
|
||||
print(f'Error: {err}')
|
||||
percent_value = config.option('percent').value.get()
|
||||
print(f'The value is "{percent_value}"')
|
||||
|
||||
# too short
|
||||
warnings.simplefilter('always', ValueWarning)
|
||||
with warnings.catch_warnings(record=True) as warn:
|
||||
config.option('percent').value.set([20, 70])
|
||||
if warn:
|
||||
warn[0].message.prefix = ''
|
||||
print(f'Warning: {warn[0].message}')
|
||||
percent_value = config.option('percent').value.get()
|
||||
print(f'The value is "{percent_value}"')
|
||||
|
||||
# correct
|
||||
config.option('percent').value.set([20, 80])
|
||||
percent_value = config.option('percent').value.get()
|
||||
print(f'The value is "{percent_value}"')
|
BIN
docs/storage.png
Normal file
After Width: | Height: | Size: 16 KiB |
265
docs/storage.svg
Normal file
|
@ -0,0 +1,265 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="400"
|
||||
height="200"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.48.4 r9939"
|
||||
sodipodi:docname="test.svg"
|
||||
inkscape:export-filename="/home/gnunux/git/tiramisu/doc/storage.png"
|
||||
inkscape:export-xdpi="135"
|
||||
inkscape:export-ydpi="135">
|
||||
<defs
|
||||
id="defs4">
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="0 : 526.18109 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_z="744.09448 : 526.18109 : 1"
|
||||
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
|
||||
id="perspective3827" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="1"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1"
|
||||
inkscape:cx="106.95445"
|
||||
inkscape:cy="208.15932"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1600"
|
||||
inkscape:window-height="841"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-852.36218)">
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 235.5,78.588237 306,109"
|
||||
id="path4403"
|
||||
inkscape:connector-type="polyline"
|
||||
inkscape:connector-curvature="0"
|
||||
inkscape:connection-start="#g4211"
|
||||
inkscape:connection-start-point="d4"
|
||||
transform="translate(0,852.36218)" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 235.5,131.08416 305,107"
|
||||
id="path4405"
|
||||
inkscape:connector-type="polyline"
|
||||
inkscape:connector-curvature="0"
|
||||
inkscape:connection-start="#g4216"
|
||||
inkscape:connection-start-point="d4"
|
||||
transform="translate(0,852.36218)" />
|
||||
<g
|
||||
id="g4206"
|
||||
transform="translate(-17,590)">
|
||||
<text
|
||||
sodipodi:linespacing="686.00001%"
|
||||
id="text2985"
|
||||
y="368.36218"
|
||||
x="98"
|
||||
style="font-size:10px;font-style:normal;font-weight:normal;line-height:686.00001335%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
xml:space="preserve"><tspan
|
||||
y="368.36218"
|
||||
x="98"
|
||||
id="tspan2987"
|
||||
sodipodi:role="line">Config</tspan></text>
|
||||
<rect
|
||||
y="351.36218"
|
||||
x="81"
|
||||
height="30"
|
||||
width="63"
|
||||
id="rect3757"
|
||||
style="fill:none;stroke:#000000;stroke-linejoin:round;stroke-opacity:1" />
|
||||
</g>
|
||||
<g
|
||||
id="g4211"
|
||||
transform="translate(-17,590)">
|
||||
<rect
|
||||
y="312.36218"
|
||||
x="189.5"
|
||||
height="30"
|
||||
width="63"
|
||||
id="rect3757-2"
|
||||
style="fill:none;stroke:#000000;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<text
|
||||
sodipodi:linespacing="686.00001%"
|
||||
id="text3777"
|
||||
y="330.36218"
|
||||
x="206"
|
||||
style="font-size:10px;font-style:normal;font-weight:normal;line-height:686.00001335%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
xml:space="preserve"><tspan
|
||||
y="330.36218"
|
||||
x="206"
|
||||
id="tspan3779"
|
||||
sodipodi:role="line">Values</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
id="g4216"
|
||||
transform="translate(-17,590)">
|
||||
<rect
|
||||
y="389.36218"
|
||||
x="189.5"
|
||||
height="30"
|
||||
width="63"
|
||||
id="rect3757-4"
|
||||
style="fill:none;stroke:#000000;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<text
|
||||
sodipodi:linespacing="686.00001%"
|
||||
id="text3799"
|
||||
y="407.36218"
|
||||
x="200"
|
||||
style="font-size:10px;font-style:normal;font-weight:normal;line-height:686.00001335%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
xml:space="preserve"><tspan
|
||||
y="407.36218"
|
||||
x="200"
|
||||
id="tspan3801"
|
||||
sodipodi:role="line">Settings</tspan></text>
|
||||
</g>
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 127,967.39444 45.5,15.93548"
|
||||
id="path4028"
|
||||
inkscape:connector-type="polyline"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 127,945.0396 45.5,-16.35484"
|
||||
id="path4030"
|
||||
inkscape:connector-type="polyline"
|
||||
inkscape:connector-curvature="0" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
id="rect4161"
|
||||
width="55.5"
|
||||
height="26"
|
||||
x="277.5"
|
||||
y="946.36218" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="fill:none;stroke:#000000;stroke-width:1.96347165;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="path3843"
|
||||
sodipodi:cx="401"
|
||||
sodipodi:cy="334.86218"
|
||||
sodipodi:rx="38"
|
||||
sodipodi:ry="10.5"
|
||||
d="m 439,334.86218 a 38,10.5 0 1 1 -76,0 38,10.5 0 1 1 76,0 z"
|
||||
transform="matrix(0.71325325,0,0,0.57998971,18.66254,749.17042)" />
|
||||
<path
|
||||
transform="matrix(0.71325325,0,0,0.57998971,18.57337,775.05247)"
|
||||
sodipodi:type="arc"
|
||||
style="fill:none;stroke:#000000;stroke-width:1.96347165;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="path3843-3"
|
||||
sodipodi:cx="401"
|
||||
sodipodi:cy="334.86218"
|
||||
sodipodi:rx="38"
|
||||
sodipodi:ry="10.5"
|
||||
d="m 439,334.86218 a 38,10.5 0 1 1 -76,0 38,10.5 0 1 1 76,0 z" />
|
||||
<path
|
||||
transform="matrix(0.71325325,0,0,0.57998971,18.52879,762.07519)"
|
||||
sodipodi:type="arc"
|
||||
style="fill:none;stroke:#000000;stroke-width:1.96347165;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="path3843-3-0"
|
||||
sodipodi:cx="401"
|
||||
sodipodi:cy="334.86218"
|
||||
sodipodi:rx="38"
|
||||
sodipodi:ry="10.5"
|
||||
d="m 439,334.86218 a 38,10.5 0 1 1 -76,0 38,10.5 0 1 1 76,0 z" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
id="rect3883"
|
||||
width="62.989182"
|
||||
height="6.7061315"
|
||||
x="274.72043"
|
||||
y="949.91193" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
id="rect3883-3"
|
||||
width="58.087975"
|
||||
height="6.4161367"
|
||||
x="277.34818"
|
||||
y="962.78046" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1.26286423;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
|
||||
d="m 277.52869,943.35095 -0.0442,26.02673"
|
||||
id="path3917"
|
||||
inkscape:connector-type="polyline"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1.26286423;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
|
||||
d="m 331.64698,969.26909 0.13377,-26.17203"
|
||||
id="path3921"
|
||||
inkscape:connector-type="polyline"
|
||||
inkscape:connector-curvature="0" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10px;font-style:normal;font-weight:normal;line-height:686.00001335%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="286.33643"
|
||||
y="958.32324"
|
||||
id="text3821"
|
||||
sodipodi:linespacing="686.00001%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3823"
|
||||
x="286.33643"
|
||||
y="958.32324">Storage</tspan></text>
|
||||
<g
|
||||
id="g4201"
|
||||
transform="translate(-17,590)">
|
||||
<rect
|
||||
y="293.42468"
|
||||
x="81"
|
||||
height="30"
|
||||
width="63"
|
||||
id="rect3757-5"
|
||||
style="fill:none;stroke:#000000;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<text
|
||||
sodipodi:linespacing="100%"
|
||||
id="text4190"
|
||||
y="309.42468"
|
||||
x="110.27588"
|
||||
style="font-size:10px;font-style:normal;font-weight:normal;text-align:center;line-height:100%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
xml:space="preserve"><tspan
|
||||
id="tspan4194"
|
||||
y="309.42468"
|
||||
x="110.27588"
|
||||
sodipodi:role="line">Option</tspan></text>
|
||||
</g>
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 95.5,913.42468 0,27.9375"
|
||||
id="path4199"
|
||||
inkscape:connector-type="polyline"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 9.4 KiB |
13
docs/symlinkoption.rst
Normal file
|
@ -0,0 +1,13 @@
|
|||
====================================================
|
||||
The symbolic link option: :class:`SymLinkOption`
|
||||
====================================================
|
||||
|
||||
A `SymLinkOption` is an option that actually points to another option.
|
||||
|
||||
Each time we will access to a properties of this options, we will have in return the value of other option.
|
||||
|
||||
Creation a `SymLinkOption` is easy:
|
||||
|
||||
>>> from tiramisu import StrOption, SymLinkOption
|
||||
>>> st = StrOption('str', 'str')
|
||||
>>> sym = SymLinkOption('sym', st)
|
271
docs/validator.rst
Normal file
|
@ -0,0 +1,271 @@
|
|||
==================================
|
||||
Validator
|
||||
==================================
|
||||
|
||||
What are validator?
|
||||
==================================
|
||||
|
||||
Validator is a functionnality that allow you to call a function to determine if an option is valid.
|
||||
|
||||
To define validator we have to use :doc:`calculation` object.
|
||||
The function have to raise a `ValueError` object if the value is not valid. It could emit a warning when raises a `ValueWarning`.
|
||||
|
||||
Validator with options
|
||||
=============================
|
||||
|
||||
Here an example, where we want to ask a new password to an user. This password should not be weak. The password will be asked twice and must match.
|
||||
|
||||
First of all, import necessary object:
|
||||
|
||||
.. literalinclude:: src/validator.py
|
||||
:lines: 3-7
|
||||
:linenos:
|
||||
|
||||
Create a first function to valid that the password is not weak:
|
||||
|
||||
.. literalinclude:: src/validator.py
|
||||
:lines: 10-14
|
||||
:linenos:
|
||||
|
||||
Secondly create a function to valid password length. The password must be longer than the value of `min_len` and should be longer than the value of `recommand_len`.
|
||||
|
||||
In first case, function raise `ValueError`, this value is incorrect.
|
||||
|
||||
In second case, function raise `ValueWarning`, the value is valid but discouraged:
|
||||
|
||||
.. literalinclude:: src/validator.py
|
||||
:lines: 17-23
|
||||
:linenos:
|
||||
|
||||
Thirdly create a function that verify that the login name is not a part of password (password `foo2aZ$` if not valid for user `foo`):
|
||||
|
||||
.. literalinclude:: src/validator.py
|
||||
:lines: 26-28
|
||||
:linenos:
|
||||
|
||||
Now we can creation an option to ask user login:
|
||||
|
||||
.. literalinclude:: src/validator.py
|
||||
:lines: 36-37
|
||||
:linenos:
|
||||
|
||||
Create a calculation to launch `is_password_conform`. This function will be use in a new option and must validate this new option. So we use the object `ParamSelfOption` has parameter to retrieve the value of current option:
|
||||
|
||||
.. literalinclude:: src/validator.py
|
||||
:lines: 39-41
|
||||
:linenos:
|
||||
|
||||
Create a second calculation to launch `password_correct_len` function. We want set 8 as `min_len` value and 12 as `recommand_len` value:
|
||||
|
||||
.. literalinclude:: src/validator.py
|
||||
:lines: 43-46
|
||||
:linenos:
|
||||
|
||||
Create a third calculation to launch `user_not_in_password` function. For this function, we use keyword argument. This function normaly raise `ValueError` but in this case we want demoting this error as a simple warning. So we add `warnings_only` parameter:
|
||||
|
||||
.. literalinclude:: src/validator.py
|
||||
:lines: 48-51
|
||||
:linenos:
|
||||
|
||||
So now we can create first password option that use those calculations:
|
||||
|
||||
.. literalinclude:: src/validator.py
|
||||
:lines: 54-58
|
||||
:linenos:
|
||||
|
||||
A new function is created to conform that password1 and password2 match:
|
||||
|
||||
.. literalinclude:: src/validator.py
|
||||
:lines: 31-33
|
||||
:linenos:
|
||||
|
||||
And now we can create second password option that use this function:
|
||||
|
||||
.. literalinclude:: src/validator.py
|
||||
:lines: 60-64
|
||||
:linenos:
|
||||
|
||||
Finally we create optiondescription and config:
|
||||
|
||||
.. literalinclude:: src/validator.py
|
||||
:lines: 66-70
|
||||
:linenos:
|
||||
|
||||
Now we can test this `Config`:
|
||||
|
||||
.. literalinclude:: src/validator.py
|
||||
:lines: 72-77
|
||||
:linenos:
|
||||
|
||||
The tested password is too weak, so value is not set.
|
||||
The error is: `Error: "aAbBc" is an invalid string for "Password", please choose a stronger password, try a mix of letters, numbers and symbols`.
|
||||
|
||||
The password is part of error message. In this case it's a bad idea. So we have to remove `prefix` to the error message:
|
||||
|
||||
.. literalinclude:: src/validator.py
|
||||
:lines: 79-85
|
||||
:linenos:
|
||||
|
||||
Now the error is: `Error: please choose a stronger password, try a mix of letters, numbers and symbols`.
|
||||
|
||||
Let's try with a password not weak but too short:
|
||||
|
||||
.. literalinclude:: src/validator.py
|
||||
:lines: 87-93
|
||||
:linenos:
|
||||
|
||||
The error is: `Error: use 8 characters or more for your password`.
|
||||
|
||||
Now try a password with 8 characters:
|
||||
|
||||
.. literalinclude:: src/validator.py
|
||||
:lines: 95-104
|
||||
:linenos:
|
||||
|
||||
Warning is display but password is store:
|
||||
|
||||
`Warning: it would be better to use more than 12 characters for your password`
|
||||
|
||||
`The password is "aZ$1bN:2"`
|
||||
|
||||
Try a password with the login as part of it:
|
||||
|
||||
.. literalinclude:: src/validator.py
|
||||
:lines: 106-115
|
||||
:linenos:
|
||||
|
||||
Warning is display but password is store:
|
||||
`Warning: the login must not be part of the password`
|
||||
`The password is "aZ$1bN:2u@1Bjuser"`
|
||||
|
||||
Now try with a valid password but that doesn't match:
|
||||
|
||||
.. literalinclude:: src/validator.py
|
||||
:lines: 117-124
|
||||
:linenos:
|
||||
|
||||
An error is displayed: `Error: those passwords didn't match, try again`.
|
||||
|
||||
Finally try a valid password:
|
||||
|
||||
.. literalinclude:: src/validator.py
|
||||
:lines: 126-133
|
||||
:linenos:
|
||||
|
||||
As expected, we have `The password for "user" is "aZ$1bN:2u@1Bj"`.
|
||||
|
||||
Validator with a multi option
|
||||
================================
|
||||
|
||||
Assume we ask percentage value to an user. The sum of values mustn't be higher than 100% and shouldn't be lower than 100%.
|
||||
|
||||
Let's start by importing the objects:
|
||||
|
||||
.. literalinclude:: src/validator_multi.py
|
||||
:lines: 1-4
|
||||
:linenos:
|
||||
|
||||
Continue by writing the validation function:
|
||||
|
||||
.. literalinclude:: src/validator_multi.py
|
||||
:lines: 7-12
|
||||
:linenos:
|
||||
|
||||
And create a simple config:
|
||||
|
||||
.. literalinclude:: src/validator_multi.py
|
||||
:lines: 15-19
|
||||
:linenos:
|
||||
|
||||
Now try with bigger sum:
|
||||
|
||||
.. literalinclude:: src/validator_multi.py
|
||||
:lines: 22-29
|
||||
:linenos:
|
||||
|
||||
The result is:
|
||||
|
||||
`Error: the total 110% is bigger than 100%`
|
||||
|
||||
`The value is "[]"`
|
||||
|
||||
Let's try with lower sum:
|
||||
|
||||
.. literalinclude:: src/validator_multi.py
|
||||
:lines: 31-39
|
||||
:linenos:
|
||||
|
||||
The result is:
|
||||
|
||||
`Warning: the total 90% is lower than 100%`
|
||||
|
||||
`The value is "[20, 70]"`
|
||||
|
||||
Finally with correct value:
|
||||
|
||||
.. literalinclude:: src/validator_multi.py
|
||||
:lines: 41-44
|
||||
:linenos:
|
||||
|
||||
The result is:
|
||||
|
||||
`The value is "[20, 80]"`
|
||||
|
||||
Validator with a follower option
|
||||
==================================
|
||||
|
||||
Assume we want distribute something to differents users. The sum of values mustn't be higher than 100%.
|
||||
|
||||
First, import all needed objects:
|
||||
|
||||
.. literalinclude:: src/validator_follower.py
|
||||
:lines: 1-4
|
||||
:linenos:
|
||||
|
||||
Let's start to write a function with three arguments:
|
||||
|
||||
- the first argument will have all values set for the follower
|
||||
- the second argument will have only last value set for the follower
|
||||
- the third argument will have the index
|
||||
|
||||
.. literalinclude:: src/validator_follower.py
|
||||
:lines: 7-12
|
||||
:linenos:
|
||||
|
||||
Continue by creating a calculation:
|
||||
|
||||
.. literalinclude:: src/validator_follower.py
|
||||
:lines: 15-17
|
||||
:linenos:
|
||||
|
||||
And instanciate differents option and config:
|
||||
|
||||
.. literalinclude:: src/validator_follower.py
|
||||
:lines: 20-26
|
||||
:linenos:
|
||||
|
||||
Add two value to the leader:
|
||||
|
||||
.. literalinclude:: src/validator_follower.py
|
||||
:lines: 29
|
||||
:linenos:
|
||||
|
||||
The user user1 will have 20%:
|
||||
|
||||
.. literalinclude:: src/validator_follower.py
|
||||
:lines: 30
|
||||
:linenos:
|
||||
|
||||
If we try to set 90% to user2:
|
||||
|
||||
.. literalinclude:: src/validator_follower.py
|
||||
:lines: 33-38
|
||||
:linenos:
|
||||
|
||||
This error occured: `Error: the value 90 (at index 1) is too big, the total is 110%`
|
||||
|
||||
No problem with 80%:
|
||||
|
||||
.. literalinclude:: src/validator_follower.py
|
||||
:lines: 40-41
|
||||
:linenos:
|