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:
|
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:
|
Let's import needed object:
|
||||||
|
|
||||||
```python
|
.. literalinclude:: src/api_value.py
|
||||||
from asyncio import run
|
:lines: 1-4
|
||||||
from shutil import disk_usage
|
:linenos:
|
||||||
from os.path import isdir
|
|
||||||
from tiramisu import FilenameOption, FloatOption, OptionDescription, Config, \
|
|
||||||
Calculation, Params, ParamValue, ParamOption, ParamSelfOption
|
|
||||||
```
|
|
||||||
|
|
||||||
Create a function that verify the path exists in current system:
|
Create a function that verify the path exists in current system:
|
||||||
|
|
||||||
```python
|
.. literalinclude:: src/api_value.py
|
||||||
def valid_is_dir(path):
|
:lines: 6-9
|
||||||
# verify if path is a directory
|
:linenos:
|
||||||
if not isdir(path):
|
|
||||||
raise ValueError('this directory does not exist')
|
|
||||||
```
|
|
||||||
|
|
||||||
Use this function as a :doc:`validator` in a new option call `path`:
|
Use this function as a :doc:`validator` in a new option call `path`:
|
||||||
|
|
||||||
```python
|
.. literalinclude:: src/api_value.py
|
||||||
filename = FilenameOption('path', 'Path', validators=[Calculation(valid_is_dir,
|
:lines: 24-25
|
||||||
Params(ParamSelfOption()))])
|
:linenos:
|
||||||
```
|
|
||||||
|
|
||||||
Create a second function that calculate the disk usage:
|
Create a second function that calculate the disk usage:
|
||||||
|
|
||||||
```python
|
.. literalinclude:: src/api_value.py
|
||||||
def calc_disk_usage(path, size='bytes'):
|
:lines: 11-21
|
||||||
# do not calc if path is None
|
:linenos:
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
Add a new option call `usage` that use this function with first argument the option `path` created before:
|
Add a new option call `usage` that use this function with first argument the option `path` created before:
|
||||||
|
|
||||||
```python
|
.. literalinclude:: src/api_value.py
|
||||||
usage = FloatOption('usage', 'Disk usage', Calculation(calc_disk_usage,
|
:lines: 26-27
|
||||||
Params(ParamOption(filename))))
|
:linenos:
|
||||||
```
|
|
||||||
|
|
||||||
Finally add those options in option description and a Config:
|
Finally add those options in option description and a Config:
|
||||||
|
|
||||||
```python
|
.. literalinclude:: src/api_value.py
|
||||||
disk = OptionDescription('disk', 'Verify disk usage', [filename, usage])
|
:lines: 28-31
|
||||||
root = OptionDescription('root', 'root', [disk])
|
:linenos:
|
||||||
async def main():
|
|
||||||
config = await Config(root)
|
|
||||||
await config.property.read_write()
|
|
||||||
return config
|
|
||||||
|
|
||||||
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:
|
First of all, retrieve the values of both options:
|
||||||
|
|
||||||
```python
|
>>> config.option('disk.path').value.get()
|
||||||
async def main():
|
|
||||||
print(await config.option('disk.path').value.get())
|
|
||||||
print(await config.option('disk.usage').value.get())
|
|
||||||
|
|
||||||
run(main())
|
|
||||||
```
|
|
||||||
|
|
||||||
returns:
|
|
||||||
|
|
||||||
```
|
|
||||||
None
|
None
|
||||||
|
>>> config.option('disk.usage').value.get()
|
||||||
None
|
None
|
||||||
```
|
|
||||||
|
|
||||||
Enter a value of the `path` option:
|
Enter a value of the `path` option:
|
||||||
|
|
||||||
```python
|
>>> config.option('disk.path').value.set('/')
|
||||||
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())
|
|
||||||
|
|
||||||
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
|
668520882176.0
|
||||||
```
|
|
||||||
|
|
||||||
When you enter a value it is validated:
|
When you enter a value it is validated:
|
||||||
|
|
||||||
```python
|
>>> try:
|
||||||
async def main():
|
>>> config.option('disk.path').value.set('/unknown')
|
||||||
try:
|
>>> except ValueError as err:
|
||||||
await config.option('disk.path').value.set('/unknown')
|
>>> print(err)
|
||||||
except ValueError as err:
|
|
||||||
print(err)
|
|
||||||
|
|
||||||
run(main())
|
|
||||||
```
|
|
||||||
|
|
||||||
returns:
|
|
||||||
|
|
||||||
```
|
|
||||||
"/unknown" is an invalid file name for "Path", this directory does not exist
|
"/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:
|
To check is a value is valid:
|
||||||
|
|
||||||
```python
|
>>> config.option('disk.path').value.valid()
|
||||||
await 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:
|
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()
|
>>> config.option('disk.usage').value.default()
|
||||||
668510105600.0
|
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:
|
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()
|
>>> config.option('disk.path').value.get()
|
||||||
None
|
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.
|
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()
|
>>> config.option('disk.path').owner.get()
|
||||||
itsme
|
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`.
|
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:
|
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
|
:lines: 26-31
|
||||||
:linenos:
|
:linenos:
|
||||||
|
|
||||||
We set the default value to `bytes`, if not, the default value will be None.
|
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:
|
At any time, we can get all de choices avalaible for an option:
|
||||||
|
|
||||||
>>> config.option('disk.size_type').value.list()
|
>>> config.option('disk.size_type').value.list()
|
||||||
('bytes', 'giga bytes')
|
('bytes', 'giga bytes')
|
||||||
|
|
||||||
### Value in multi option
|
Value in multi option
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
.. FIXME undefined
|
.. FIXME undefined
|
||||||
|
|
||||||
|
@ -237,25 +208,26 @@ First of all, we have to modification in this option:
|
||||||
- add multi attribute to True
|
- 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
|
- 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
|
:lines: 23-25
|
||||||
:linenos:
|
:linenos:
|
||||||
|
|
||||||
Secondly, the function calc_disk_usage must return a list:
|
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
|
:lines: 11-26
|
||||||
:linenos:
|
:linenos:
|
||||||
|
|
||||||
Finally `usage` option is also a multi:
|
Finally `usage` option is also a multi:
|
||||||
|
|
||||||
.. literalinclude:: ../src/api_value_multi.py
|
.. literalinclude:: src/api_value_multi.py
|
||||||
:lines: 27-30
|
:lines: 27-30
|
||||||
:linenos:
|
: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:
|
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()
|
>>> config.option('disk.usage').value.get()
|
||||||
[668499898368.0, 8279277568.0]
|
[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:
|
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()
|
>>> config.option('disk.path').owner.get()
|
||||||
user
|
user
|
||||||
|
|
||||||
### Leadership
|
Leadership
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
In previous example, we cannot define different `size_type` for each path. If you want do this, you need a 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:
|
So let's change this function:
|
||||||
|
|
||||||
.. literalinclude:: ../src/api_value_leader.py
|
.. literalinclude:: src/api_value_leader.py
|
||||||
:lines: 12-18
|
:lines: 12-18
|
||||||
:linenos:
|
:linenos:
|
||||||
|
|
||||||
Secondly the option `size_type` became a multi:
|
Secondly the option `size_type` became a multi:
|
||||||
|
|
||||||
.. literalinclude:: ../src/api_value_leader.py
|
.. literalinclude:: src/api_value_leader.py
|
||||||
:lines: 24-25
|
:lines: 24-25
|
||||||
:linenos:
|
:linenos:
|
||||||
|
|
||||||
Finally disk has to be a leadership:
|
Finally disk has to be a leadership:
|
||||||
|
|
||||||
.. literalinclude:: ../src/api_value_leader.py
|
.. literalinclude:: src/api_value_leader.py
|
||||||
:lines: 30
|
:lines: 30
|
||||||
:linenos:
|
:linenos:
|
||||||
|
|
||||||
#### Get and set a leader
|
Get and set a leader
|
||||||
|
'''''''''''''''''''''''''''''
|
||||||
|
|
||||||
A leader is, in fact, a multi option:
|
A leader is, in fact, a multi option:
|
||||||
|
|
||||||
|
@ -346,7 +321,8 @@ To reduce use the `pop` method:
|
||||||
>>> config.option('disk.path').value.get()
|
>>> 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:
|
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()
|
>>> config.option('disk.size_type').value.len()
|
||||||
2
|
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:
|
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()
|
>>> config.option('disk.size_type', 1).owner.get()
|
||||||
default
|
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:
|
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.path').value.set(['/', '/tmp'])
|
||||||
>>> config.option('disk.size_type', 0).value.set('giga bytes')
|
>>> config.option('disk.size_type', 0).value.set('giga bytes')
|
||||||
>>> config.option('disk').value.dict()
|
>>> config.option('disk').value.get()
|
||||||
{'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)
|
|
||||||
{'disk.path': ['/', '/tmp'], 'disk.size_type': ['giga bytes', 'bytes'], 'disk.usage': [622.578239440918, 8279273472.0]}
|
{'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:
|
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.path').value.set(['/', '/tmp'])
|
||||||
>>> config.option('disk.size_type', 0).value.set('giga bytes')
|
>>> 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]}
|
{'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:
|
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]}
|
{'path': ['/', '/tmp'], 'size_type': ['giga bytes', 'bytes'], 'usage': [622.578239440918, 8279273472.0]}
|
||||||
|
|
||||||
### importation/exportation
|
importation/exportation
|
||||||
|
------------------------
|
||||||
|
|
||||||
In config, we can export full values:
|
In config, we can export full values:
|
||||||
|
|
||||||
|
@ -439,4 +415,3 @@ and reimport it later:
|
||||||
>>> config.value.importation(export)
|
>>> 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.
|
.. 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,
|
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
|
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.
|
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
|
Tiramisu is an options handler and an options controller, which aims at
|
||||||
producing flexible and fast options access. The main advantages are its access
|
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
|
validations towards the whole options. Furthermore, options can be reached and
|
||||||
changed according to the access rules from nearly everywhere.
|
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:
|
And then type:
|
||||||
|
|
||||||
```bash
|
.. code-block:: bash
|
||||||
$ pip install tiramisu
|
|
||||||
```
|
|
||||||
|
|
||||||
### 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`.
|
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.
|
We suggest using `git` if one wants to access to the current developments.
|
||||||
|
|
||||||
```bash
|
.. code-block:: bash
|
||||||
$ git clone https://framagit.org/tiramisu/tiramisu.git
|
|
||||||
```
|
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
|
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
|
||||||
|
|
||||||
|
The tasting of `Tiramisu` --- `user documentation`
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
.. image:: logo.png
|
||||||
|
:height: 150px
|
||||||
|
|
||||||
|
`Tiramisu`
|
||||||
|
|
||||||
- is a cool, refreshing Italian dessert,
|
- is a cool, refreshing Italian dessert,
|
||||||
- it is also an [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.
|
- 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::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
gettingstarted
|
||||||
|
config
|
||||||
|
browse
|
||||||
|
api_value
|
||||||
api_property
|
api_property
|
||||||
storage
|
|
||||||
application
|
application
|
||||||
quiz
|
quiz
|
||||||
glossary
|
glossary
|
||||||
|
|
||||||
External project:
|
.. External project:
|
||||||
|
..
|
||||||
.. toctree::
|
.. .. toctree::
|
||||||
:maxdepth: 2
|
.. :maxdepth: 2
|
||||||
|
..
|
||||||
cmdline_parser
|
.. 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"
|
.. 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:
|