Compare commits
42 commits
feature/4.
...
main
Author | SHA1 | Date | |
---|---|---|---|
1a31ada869 | |||
4aecf2c4bd | |||
a3228fbf30 | |||
c3bb590415 | |||
16a7857d0c | |||
952069a865 | |||
5e66d2074d | |||
b5d477a439 | |||
54ac0c980a | |||
be8b1e7e4f | |||
7ae1b48f4a | |||
7761758096 | |||
bbec439a5e | |||
9c36fb8fb2 | |||
b5346f9c57 | |||
f4f5fb79e4 | |||
c2e3bf86f1 | |||
f2893aaacd | |||
4e1053bba9 | |||
897d4dd216 | |||
8e743131cd | |||
ccf8ebc91e | |||
ff4f5abae4 | |||
7f2d6b03e0 | |||
93fa26f8df | |||
428e243630 | |||
1a5e4e37d1 | |||
de9485b74e | |||
90564d4983 | |||
6b473e63f2 | |||
059c5f7407 | |||
73c8db5839 | |||
4b052c3943 | |||
0b2f13404c | |||
c74c346f09 | |||
e09ca78487 | |||
0b1d1ef3f1 | |||
e45a1910d9 | |||
a3d04c7451 | |||
32b24c2978 | |||
a3261abc94 | |||
e7b174f28f |
6
.cz.toml
Normal file
|
@ -0,0 +1,6 @@
|
|||
[tool.commitizen]
|
||||
name = "cz_conventional_commits"
|
||||
tag_format = "$version"
|
||||
version_scheme = "semver"
|
||||
version = "4.1.0"
|
||||
update_changelog_on_bump = true
|
1
.gitignore
vendored
|
@ -1,6 +1,5 @@
|
|||
*~
|
||||
*#
|
||||
*.pyc
|
||||
*.mo
|
||||
*.swp
|
||||
build/
|
||||
|
|
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
|
40
CHANGELOG.md
Normal file
|
@ -0,0 +1,40 @@
|
|||
## 5.1.0rc0 (2024-11-06)
|
||||
|
||||
### Feat
|
||||
|
||||
- add min_len, max_len, forbidden_char for password option
|
||||
|
||||
## 5.0.0 (2024-11-01)
|
||||
|
||||
### Fix
|
||||
|
||||
- add changelog_merge_prerelease to commitizen
|
||||
|
||||
## 5.0.0rc0 (2024-11-01)
|
||||
|
||||
### Feat
|
||||
|
||||
- return propertyerror in function
|
||||
- mandatory for a variable and replace suffix to identifier
|
||||
- reorganise code
|
||||
|
||||
### Fix
|
||||
|
||||
- translation + black
|
||||
- regexp tests
|
||||
- update documentation
|
||||
- better permissive support
|
||||
- 4.1.0
|
||||
|
||||
## 4.0.2 (2024-02-20)
|
||||
|
||||
### Fix
|
||||
|
||||
- sub dynamic in sub dynamic family
|
||||
|
||||
## 4.0.1 (2023-12-17)
|
||||
|
||||
### Feat
|
||||
|
||||
- documentation
|
||||
- dynamic family can have sub family
|
|
@ -1,3 +1,9 @@
|
|||
Mon Dec 14 21:39:30 2023 +0200 Emmanuel Garette <egarette@silique.fr>
|
||||
* add mission dependency for setuptools
|
||||
|
||||
Mon Dec 12 20:30:30 2023 +0200 Emmanuel Garette <egarette@silique.fr>
|
||||
* version 4.0
|
||||
|
||||
Mon Apr 1 11:47:30 2019 +0200 Emmanuel Garette <egarette@cadoles.com>
|
||||
* version 3.0 rc16
|
||||
* tiramisu is now async
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
# Include the README
|
||||
include *.rst
|
||||
|
||||
# Include the license file
|
||||
include LICENSE.txt
|
||||
|
||||
# Include the data files
|
||||
recursive-include tiramisu *.py *.mo
|
18
README.md
|
@ -1,13 +1,25 @@
|
|||
![Logo Tiramisu](logo.png "logo Tiramisu")
|
||||
|
||||
An options controller tool
|
||||
-------------------------------------
|
||||
|
||||
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
|
||||
options to where they are actually used and even more annoying to add new
|
||||
options. To circumvent these problems the configuration control was
|
||||
introduced...
|
||||
|
||||
Tiramisu is an options handler and an options controller, wich aims at
|
||||
producing flexible and fast options access.
|
||||
|
||||
|
||||
[Documentations](doc/README.md)
|
||||
|
||||
|
||||
# LICENSES
|
||||
|
||||
See COPYING for the licences of the code and the documentation.
|
||||
See [COPYING](COPYING) for the licences of the code and the documentation.
|
||||
|
||||
See AUTHORS for the details about the tiramisu's team.
|
||||
See [AUTHORS](AUTHORS) for the details about the tiramisu's team.
|
||||
|
||||
|
||||
|
||||
|
|
350
doc/browse.md
|
@ -1,350 +0,0 @@
|
|||
# Browse the Config
|
||||
|
||||
## Getting the options
|
||||
|
||||
Create a simple Config:
|
||||
|
||||
```python
|
||||
from asyncio import run
|
||||
from tiramisu import Config
|
||||
from tiramisu import StrOption, OptionDescription
|
||||
|
||||
# let's declare some options
|
||||
var1 = StrOption('var1', 'first option')
|
||||
# an option with a default value
|
||||
var2 = StrOption('var2', 'second option', 'value')
|
||||
# let's create a group of options
|
||||
od1 = OptionDescription('od1', 'first OD', [var1, var2])
|
||||
|
||||
# let's create another group of options
|
||||
rootod = OptionDescription('rootod', '', [od1])
|
||||
|
||||
async def main():
|
||||
# let's create the config
|
||||
cfg = await Config(rootod)
|
||||
# the api is read only
|
||||
await cfg.property.read_only()
|
||||
# the read_write api is available
|
||||
await cfg.property.read_write()
|
||||
return cfg
|
||||
|
||||
cfg = run(main())
|
||||
```
|
||||
|
||||
We retrieve by path an option named "var1" and then we retrieve its name and its docstring:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
print(await cfg.option('od1.var1').option.name())
|
||||
print(await cfg.option('od1.var1').option.doc())
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
var1
|
||||
first option
|
||||
```
|
||||
|
||||
## Accessing the values of the options
|
||||
|
||||
Let's browse the configuration structure and option values.
|
||||
|
||||
You have getters as a "get" method on option objects:
|
||||
|
||||
1. getting all the options
|
||||
|
||||
```python
|
||||
async def main():
|
||||
print(await cfg.value.dict())
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
{'var1': None, 'var2': 'value'}
|
||||
```
|
||||
|
||||
2. getting the "od1" option description
|
||||
|
||||
```python
|
||||
async def main():
|
||||
print(await cfg.option('od1').value.dict())
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
{'od1.var1': None, 'od1.var2': 'value'}
|
||||
```
|
||||
|
||||
3. getting the var1 option's value
|
||||
|
||||
```python
|
||||
async def main():
|
||||
print(await cfg.option('od1.var1').value.get())
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
None
|
||||
```
|
||||
|
||||
4. getting the var2 option's default value
|
||||
|
||||
```python
|
||||
async def main():
|
||||
print(await cfg.option('od1.var2').value.get())
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
value
|
||||
```
|
||||
|
||||
5. trying to get a non existent option's value
|
||||
|
||||
```python
|
||||
async def main():
|
||||
try:
|
||||
await cfg.option('od1.idontexist').value.get()
|
||||
except AttributeError as err:
|
||||
print(str(err))
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
unknown option "idontexist" in optiondescription "first OD"
|
||||
```
|
||||
|
||||
## Setting the value of an option
|
||||
|
||||
An important part of the setting's configuration consists of setting the
|
||||
value's option.
|
||||
|
||||
You have setters as a "set" method on option objects.
|
||||
|
||||
And if you wanna come back to a default value, use the "reset()" method.
|
||||
|
||||
1. changing the "od1.var1" value
|
||||
|
||||
```python
|
||||
async def main():
|
||||
await cfg.option('od1.var1').value.set('éééé')
|
||||
print(await cfg.option('od1.var1').value.get())
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
éééé
|
||||
```
|
||||
|
||||
2. carefull to the type of the value to be set
|
||||
|
||||
```python
|
||||
async def main():
|
||||
try:
|
||||
await cfg.option('od1.var1').value.set(23454)
|
||||
except ValueError as err:
|
||||
print(str(err))
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
"23454" is an invalid string for "first option"
|
||||
```
|
||||
|
||||
3. let's come back to the default value
|
||||
|
||||
```python
|
||||
async def main():
|
||||
await cfg.option('od1.var2').value.reset()
|
||||
print(await cfg.option('od1.var2').value.get())
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
value
|
||||
```
|
||||
|
||||
> **_Important_** If the config is "read only", setting an option's value isn't allowed, see [property](property.md).
|
||||
|
||||
Let's make the protocol of accessing a Config's option explicit
|
||||
(because explicit is better than implicit):
|
||||
|
||||
1. If the option has not been declared, an "Error" is raised,
|
||||
2. If an option is declared, but neither a value nor a default value has
|
||||
been set, the returned value is "None",
|
||||
3. If an option is declared and a default value has been set, but no value
|
||||
has been set, the returned value is the default value of the option,
|
||||
|
||||
4. If an option is declared, and a value has been set, the returned value is
|
||||
the value of the option.
|
||||
|
||||
But there are special exceptions. We will see later on that an option can be a
|
||||
mandatory option. A mandatory option is an option that must have a value
|
||||
defined.
|
||||
|
||||
Searching for an option
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In an application, knowing the path of an option is not always feasible.
|
||||
That's why a tree of options can easily be searched with the "find()" method.
|
||||
|
||||
```python
|
||||
from asyncio import run
|
||||
from tiramisu import Config
|
||||
from tiramisu import OptionDescription, StrOption
|
||||
from tiramisu.setting import undefined
|
||||
|
||||
var1 = StrOption('var1', '')
|
||||
var2 = StrOption('var2', '')
|
||||
var3 = StrOption('var3', '')
|
||||
od1 = OptionDescription('od1', '', [var1, var2, var3])
|
||||
var4 = StrOption('var4', '')
|
||||
var5 = StrOption('var5', '')
|
||||
var6 = StrOption('var6', '')
|
||||
var7 = StrOption('var1', '', 'value')
|
||||
od2 = OptionDescription('od2', '', [var4, var5, var6, var7])
|
||||
rootod = OptionDescription('rootod', '', [od1, od2])
|
||||
```
|
||||
|
||||
Let's find an option by it's name
|
||||
|
||||
And let's find first an option by it's name
|
||||
|
||||
The search can be performed in a subtree
|
||||
|
||||
```python
|
||||
async def main():
|
||||
cfg = await Config(rootod)
|
||||
print(await cfg.option.find(name='var1'))
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
[<tiramisu.api.TiramisuOption object at 0x7f490a530f98>, <tiramisu.api.TiramisuOption object at 0x7f490a530748>]
|
||||
```
|
||||
|
||||
If the option name is unique, the search can be stopped once one matched option has been found:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
cfg = await Config(rootod)
|
||||
print(await cfg.option.find(name='var1', first=True))
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
<tiramisu.api.TiramisuOption object at 0x7ff27fc93c70>
|
||||
```
|
||||
|
||||
Search object behaves like a cfg object, for example:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
cfg = await Config(rootod)
|
||||
option = await cfg.option.find(name='var1', first=True)
|
||||
print(await option.option.name())
|
||||
print(await option.option.doc())
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
var1
|
||||
var1
|
||||
```
|
||||
|
||||
Search can be made with various criteria:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
cfg = await Config(rootod)
|
||||
await cfg.option.find(name='var3', value=undefined)
|
||||
await cfg.option.find(name='var3', type=StrOption)
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
The find method can be used in subconfigs:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
cfg = await Config(rootod)
|
||||
print(await cfg.option('od2').find('var1'))
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
## The "dict" flattening utility
|
||||
|
||||
In a config or a subconfig, you can print a dict-like representation
|
||||
|
||||
In a "fullpath" or a "flatten" way
|
||||
|
||||
- get the "od1" option description:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
cfg = await Config(rootod)
|
||||
print(await cfg.option('od1').value.dict(fullpath=True))
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
{'od1.var1': None, 'od1.var2': None, 'od1.var3': None}
|
||||
```
|
||||
|
||||
```python
|
||||
async def main():
|
||||
cfg = await Config(rootod)
|
||||
print(await cfg.option('od1').value.dict(fullpath=False))
|
||||
|
||||
run(main())
|
||||
>>> print(cfg.option('od1').value.dict(fullpath=True))
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
{'var1': None, 'var2': None, 'var3': None}
|
||||
```
|
||||
|
||||
> **_NOTE_** be carefull with this "flatten" parameter, because we can just loose some options if there are same name (some option can be overriden).
|
||||
|
107
doc/config.md
|
@ -1,107 +0,0 @@
|
|||
# The Config
|
||||
|
||||
Tiramisu is made of almost three main classes/concepts :
|
||||
|
||||
- the "Option" stands for the option types
|
||||
- the "OptionDescription" is the schema, the option's structure
|
||||
- the "Config" which is the whole configuration entry point
|
||||
|
||||
![The Config](config.png "The Config")
|
||||
|
||||
## The handling of options
|
||||
|
||||
The handling of options is split into two parts: the description of
|
||||
which options are available, what their possible values and defaults are
|
||||
and how they are organized into a tree. A specific choice of options is
|
||||
bundled into a configuration object which has a reference to its option
|
||||
description (and therefore makes sure that the configuration values
|
||||
adhere to the option description).
|
||||
|
||||
- [Instanciate an option](option.md)
|
||||
- [Default Options](options.md)
|
||||
- [The symbolic link option: SymLinkOption](symlinkoption.md)
|
||||
- [Create it's own option](own_option.md)
|
||||
|
||||
## Option description are nested Options
|
||||
|
||||
The Option (in this case the "BoolOption"),
|
||||
are organized into a tree into nested "OptionDescription" objects.
|
||||
|
||||
Every option has a name, as does every option group.
|
||||
|
||||
- [Generic container: OptionDescription](optiondescription.md)
|
||||
- [Dynamic option description: DynOptionDescription](dynoptiondescription.md)
|
||||
- [Leadership OptionDescription: Leadership](leadership.md)
|
||||
|
||||
## Config
|
||||
|
||||
Getting started with the tiramisu library (it loads and prints properly).
|
||||
|
||||
Let's perform a *Getting started* code review :
|
||||
|
||||
```python
|
||||
from asyncio import run
|
||||
from tiramisu import Config
|
||||
from tiramisu import OptionDescription, BoolOption
|
||||
|
||||
# let's create a group of options named "optgroup"
|
||||
descr = OptionDescription("optgroup", "", [
|
||||
# ... with only one option inside
|
||||
BoolOption("bool", "", default=False)
|
||||
])
|
||||
|
||||
async def main():
|
||||
# Then, we make a Config with the OptionDescription` we
|
||||
cfg = await Config(descr)
|
||||
# the global help about the config
|
||||
cfg.help()
|
||||
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
Root config object that enables us to handle the configuration options
|
||||
|
||||
Settings:
|
||||
forcepermissive Access to option without verifying permissive properties
|
||||
unrestraint Access to option without property restriction
|
||||
|
||||
Commands:
|
||||
cache Manage config cache
|
||||
config Actions to Config
|
||||
information Manage config informations
|
||||
option Select an option
|
||||
owner Global owner
|
||||
permissive Manage config permissives
|
||||
property Manage config properties
|
||||
session Manage Config session
|
||||
value Manage config value
|
||||
```
|
||||
|
||||
Then let's print our "Option details.
|
||||
|
||||
```python
|
||||
cfg.option.help()
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
Select an option
|
||||
|
||||
Call: Select an option by path
|
||||
|
||||
Commands:
|
||||
dict Convert config and option to tiramisu format
|
||||
find Find an or a list of options
|
||||
list List options (by default list only option)
|
||||
updates Updates value with tiramisu format
|
||||
```
|
||||
|
||||
## Go futher with "Option" and "Config"
|
||||
|
||||
- [property](property.md)
|
||||
- [validator](validator.md)
|
|
@ -1,57 +0,0 @@
|
|||
# Dynamic option description: DynOptionDescription
|
||||
|
||||
## Dynamic option description's description
|
||||
|
||||
Dynamic option description is an OptionDescription which multiplies according to the return of a function.
|
||||
|
||||
First of all, an option description is a name, a description, children and suffixes.
|
||||
|
||||
### name
|
||||
|
||||
The "name" is important to retrieve this dynamic option description.
|
||||
|
||||
### description
|
||||
|
||||
The "description" allows the user to understand where this dynamic option description will contains.
|
||||
|
||||
### children
|
||||
|
||||
List of children option.
|
||||
|
||||
> **_NOTE:_** the option has to be multi option or leadership but not option description.
|
||||
|
||||
### suffixes
|
||||
|
||||
Suffixes is a [calculation](calculation.md) that return the list of suffixes used to create dynamic option description.
|
||||
|
||||
Let's try:
|
||||
|
||||
```python
|
||||
from tiramisu import StrOption, DynOptionDescription, Calculation
|
||||
def return_suffixes():
|
||||
return ['1', '2']
|
||||
|
||||
child1 = StrOption('first', 'First basic option ')
|
||||
child2 = StrOption('second', 'Second basic option ')
|
||||
DynOptionDescription('basic ',
|
||||
'Basic options ',
|
||||
[child1, child2],
|
||||
Calculation(return_suffixes))
|
||||
```
|
||||
|
||||
This example will construct:
|
||||
|
||||
- Basic options 1:
|
||||
|
||||
- First basic option 1
|
||||
- Second basic option 1
|
||||
|
||||
- Basic options 2:
|
||||
|
||||
- First basic option 2
|
||||
- Second basic option 2
|
||||
|
||||
## Dynamic option description's properties
|
||||
|
||||
See [property](property.md).
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
# Leadership OptionDescription: Leadership
|
||||
|
||||
A leadership is a special "OptionDescription" that wait a leader and one or multiple followers.
|
||||
|
||||
Leader and follower are multi option. The difference is that the length is defined by the length of the option leader.
|
||||
|
||||
If the length of leader is 3, all followers have also length 3.
|
||||
|
||||
An other different is that the follower is isolate. That means that you can only change on value on a specified index in the list of it's values.
|
||||
If a value is mark as modified in a specified index, that not affect the other values in other index.
|
||||
|
||||
## The leadership's description
|
||||
|
||||
A leadership is an "OptionDescription"
|
||||
|
||||
First of all, an option leadership is a name, a description and children.
|
||||
|
||||
### name
|
||||
|
||||
The "name" is important to retrieve this dynamic option description.
|
||||
|
||||
### description
|
||||
|
||||
The "description" allows the user to understand where this dynamic option description will contains.
|
||||
|
||||
### children
|
||||
|
||||
List of children option.
|
||||
|
||||
> **_NOTE:_** the option has to be multi or submulti option and not other option description.
|
||||
|
||||
Let's try:
|
||||
|
||||
```python
|
||||
from tiramisu import StrOption, Leadership
|
||||
users = StrOption('users', 'User', multi=True)
|
||||
passwords = StrOption('passwords', 'Password', multi=True)
|
||||
Leadership('users',
|
||||
'User allow to connect',
|
||||
[users, passwords])
|
||||
```
|
||||
|
||||
## The leadership's properties
|
||||
|
||||
See [property](property.md).
|
134
doc/option.md
|
@ -1,134 +0,0 @@
|
|||
# Instanciate an option
|
||||
|
||||
## Option's description
|
||||
|
||||
First of all, an option is a name and a description.
|
||||
|
||||
### name
|
||||
|
||||
The "name" is important to retrieve this option.
|
||||
|
||||
### description
|
||||
|
||||
The "description" allows the user to understand where this option will be used for.
|
||||
|
||||
Let's try:
|
||||
|
||||
```python
|
||||
from tiramisu import StrOption
|
||||
StrOption('welcome',
|
||||
'Welcome message to the user login')
|
||||
```
|
||||
|
||||
## Option's default value
|
||||
|
||||
For each option, we can defined a default value. This value will be the value of this option until user customize it.
|
||||
|
||||
This default value is store directly in the option. So we can, at any moment we can go back to the default value.
|
||||
|
||||
```python
|
||||
StrOption('welcome',
|
||||
'Welcome message to the user login',
|
||||
'Hey guys, welcome here!')
|
||||
```
|
||||
|
||||
The default value can be a calculation.
|
||||
|
||||
```python
|
||||
from tiramisu import Calculation
|
||||
def get_value():
|
||||
return 'Hey guys, welcome here'
|
||||
|
||||
StrOption('welcome',
|
||||
'Welcome message to the user login',
|
||||
Calculation(get_value))
|
||||
```
|
||||
|
||||
## Option with multiple value
|
||||
|
||||
### multi
|
||||
|
||||
There are cases where it can be interesting to have a list of values rather than just one.
|
||||
|
||||
The "multi" attribut is here for that.
|
||||
|
||||
In this case, the default value has to be a list:
|
||||
|
||||
```python
|
||||
StrOption('shopping_list',
|
||||
'The shopping list',
|
||||
['1 kilogram of carrots', 'leeks', '1 kilogram of potatos'],
|
||||
multi=True)
|
||||
```
|
||||
|
||||
The option could be a list of list, which is could submulti:
|
||||
|
||||
```python
|
||||
from tiramisu import submulti
|
||||
StrOption('shopping_list',
|
||||
'The shopping list',
|
||||
[['1 kilogram of carrots', 'leeks', '1 kilogram of potatos'], ['milk', 'eggs']],
|
||||
multi=submulti)
|
||||
```
|
||||
|
||||
The default value can be a calculation. For a multi, the function have to return a list or have to be in a list:
|
||||
|
||||
```python
|
||||
def get_values():
|
||||
return ['1 kilogram of carrots', 'leeks', '1 kilogram of potatos']
|
||||
|
||||
StrOption('shopping_list',
|
||||
'The shopping list',
|
||||
Calculation(get_values),
|
||||
multi=True)
|
||||
```
|
||||
|
||||
```python
|
||||
def get_a_value():
|
||||
return 'leeks'
|
||||
|
||||
StrOption('shopping_list',
|
||||
'The shopping list',
|
||||
['1 kilogram of carrots', Calculation(get_a_value), '1 kilogram of potatos'],
|
||||
multi=True)
|
||||
```
|
||||
|
||||
### default_multi
|
||||
|
||||
A second default value is available for multi option, "default_multi". This value is used when we add new value without specified a value.
|
||||
This "default_multi" must not be a list in multi purpose. For submulti, it has to be a list:
|
||||
|
||||
```python
|
||||
StrOption('shopping_list',
|
||||
'The shopping list',
|
||||
['1 kilogram of carrots', 'leeks', '1 kilogram of potatos'],
|
||||
default_multi='some vegetables',
|
||||
multi=True)
|
||||
StrOption('shopping_list',
|
||||
'The shopping list',
|
||||
[['1 kilogram of carrots', 'leeks', '1 kilogram of potatos'], ['milk', 'eggs']],
|
||||
default_multi=['some', 'vegetables'],
|
||||
multi=submulti)
|
||||
```
|
||||
|
||||
The default_multi value can be a calculation:
|
||||
|
||||
```python
|
||||
def get_a_value():
|
||||
return 'some vegetables'
|
||||
|
||||
StrOption('shopping_list',
|
||||
'The shopping list',
|
||||
['1 kilogram of carrots', 'leeks', '1 kilogram of potatos'],
|
||||
default_multi=Calculation(get_a_value),
|
||||
multi=True)
|
||||
```
|
||||
|
||||
## Other option's parameters
|
||||
|
||||
There are two other parameters.
|
||||
|
||||
We will see them later:
|
||||
|
||||
- [Property](property.md)
|
||||
- [Validator](validator.md)
|
|
@ -1,35 +0,0 @@
|
|||
# Generic container: OptionDescription
|
||||
|
||||
## Option description's description
|
||||
|
||||
First of all, an option description is a name, a description and children.
|
||||
|
||||
### name
|
||||
|
||||
The "name" is important to retrieve this option description.
|
||||
|
||||
### description
|
||||
|
||||
The "description" allows the user to understand where this option description will contains.
|
||||
|
||||
### children
|
||||
|
||||
List of children option.
|
||||
|
||||
> **_NOTE:_** the option can be an option or an other option description
|
||||
|
||||
Let's try:
|
||||
|
||||
```python
|
||||
from tiramisu import StrOption, OptionDescription
|
||||
child1 = StrOption('first', 'First basic option')
|
||||
child2 = StrOption('second', 'Second basic option')
|
||||
OptionDescription('basic',
|
||||
'Basic options',
|
||||
[child1, child2])
|
||||
```
|
||||
|
||||
## Option description's properties
|
||||
|
||||
See [property](property.md).
|
||||
|
501
doc/options.md
|
@ -1,501 +0,0 @@
|
|||
# Default Options
|
||||
|
||||
Basic options
|
||||
|
||||
## Textual option: StrOption
|
||||
|
||||
Option that accept any textual data in Tiramisu:
|
||||
|
||||
```python
|
||||
from tiramisu import StrOption
|
||||
StrOption('str', 'str', 'value')
|
||||
StrOption('str', 'str', '1')
|
||||
```
|
||||
|
||||
Other type generate an error:
|
||||
|
||||
```python
|
||||
try:
|
||||
StrOption('str', 'str', 1)
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
"1" is an invalid string for "str"
|
||||
```
|
||||
|
||||
|
||||
## Integers option: IntOption
|
||||
|
||||
Option that accept any integers number in Tiramisu:
|
||||
|
||||
```python
|
||||
from tiramisu import IntOption
|
||||
IntOption('int', 'int', 1)
|
||||
```
|
||||
|
||||
### min_number and max_number
|
||||
|
||||
This option can also verify minimal and maximal number:
|
||||
|
||||
```python
|
||||
IntOption('int', 'int', 10, min_number=10, max_number=15)
|
||||
```
|
||||
|
||||
```python
|
||||
try:
|
||||
IntOption('int', 'int', 16, max_number=15)
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
"16" is an invalid integer for "int", value must be less than "15"
|
||||
```
|
||||
|
||||
> **_NOTE:_** If "warnings_only" parameter it set to True, it will only emit a warning.
|
||||
|
||||
## Floating point number option: FloatOption
|
||||
|
||||
Option that accept any floating point number in Tiramisu:
|
||||
|
||||
```python
|
||||
from tiramisu import FloatOption
|
||||
FloatOption('float', 'float', 10.1)
|
||||
```
|
||||
|
||||
## Boolean option: BoolOption
|
||||
|
||||
Boolean values are the two constant objects False and True:
|
||||
|
||||
```python
|
||||
from tiramisu import BoolOption
|
||||
BoolOption('bool', 'bool', True)
|
||||
BoolOption('bool', 'bool', False)
|
||||
```
|
||||
|
||||
## Choice option: ChoiceOption
|
||||
|
||||
Option that only accepts a list of possible choices.
|
||||
|
||||
For example, we just want allowed 1 or 'see later':
|
||||
|
||||
```python
|
||||
from tiramisu import ChoiceOption
|
||||
ChoiceOption('choice', 'choice', (1, 'see later'), 1)
|
||||
ChoiceOption('choice', 'choice', (1, 'see later'), 'see later')
|
||||
```
|
||||
|
||||
Any other value isn't allowed:
|
||||
|
||||
```python
|
||||
try:
|
||||
ChoiceOption('choice', 'choice', (1, 'see later'), "i don't know")
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
"i don't know" is an invalid choice for "choice", only "1" and "see later" are allowed
|
||||
```
|
||||
|
||||
# Network options
|
||||
|
||||
## IPv4 address option: IPOption
|
||||
|
||||
An Internet Protocol address (IP address) is a numerical label assigned to each device connected to a computer network that uses the Internet Protocol for communication.
|
||||
|
||||
This option only support version 4 of the Internet Protocol.
|
||||
|
||||
```python
|
||||
from tiramisu import IPOption
|
||||
IPOption('ip', 'ip', '192.168.0.24')
|
||||
```
|
||||
|
||||
### private_only
|
||||
|
||||
By default IP could be a private or a public address. It's possible to restrict to only private IPv4 address with "private_only" attributs:
|
||||
|
||||
```python
|
||||
try:
|
||||
IPOption('ip', 'ip', '1.1.1.1', private_only=True)
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
"1.1.1.1" is an invalid IP for "ip", must be private IP
|
||||
```
|
||||
|
||||
> **_NOTE:_** If "warnings_only" parameter it set to True, it will only emit a warning.
|
||||
|
||||
### allow_reserved
|
||||
|
||||
By default, IETF reserved are not allowed. The "allow_reserved" can be used to allow this address:
|
||||
|
||||
```python
|
||||
try:
|
||||
IPOption('ip', 'ip', '255.255.255.255')
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
"255.255.255.255" is an invalid IP for "ip", mustn't be reserved IP
|
||||
```
|
||||
|
||||
But this doesn't raises:
|
||||
|
||||
```python
|
||||
IPOption('ip', 'ip', '255.255.255.255', allow_reserved=True)
|
||||
```
|
||||
|
||||
> **_NOTE:_** If "warnings_only" parameter it set to True, it will only emit a warning.
|
||||
|
||||
### cidr
|
||||
|
||||
Classless Inter-Domain Routing (CIDR) is a method for allocating IP addresses and IP routing.
|
||||
|
||||
CIDR notation, in which an address or routing prefix is written with a suffix indicating the number of bits of the prefix, such as 192.168.0.1/24.
|
||||
|
||||
```python
|
||||
IPOption('ip', 'ip', '192.168.0.1/24', cidr=True)
|
||||
try:
|
||||
IPOption('ip', 'ip', '192.168.0.0/24', cidr=True)
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
"192.168.0.0/24" is an invalid IP for "ip", it's in fact a network address
|
||||
```
|
||||
|
||||
## Port option: PortOption
|
||||
|
||||
A port is a network communication endpoint.
|
||||
|
||||
A port is a string object:
|
||||
|
||||
```python
|
||||
from tiramisu import PortOption
|
||||
PortOption('port', 'port', '80')
|
||||
```
|
||||
|
||||
### allow_range
|
||||
|
||||
A range is a list of port where we specified first port and last port number. The separator is ":":
|
||||
|
||||
```python
|
||||
PortOption('port', 'port', '2000', allow_range=True)
|
||||
PortOption('port', 'port', '2000:3000', allow_range=True)
|
||||
```
|
||||
|
||||
### allow_zero
|
||||
|
||||
By default, port 0 is not allowed, if you want allow it, use the parameter "allow_zero":
|
||||
|
||||
```python
|
||||
try:
|
||||
PortOption('port', 'port', '0')
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```python
|
||||
"0" is an invalid port for "port", must be between 1 and 49151
|
||||
```
|
||||
|
||||
But this doesn't raises:
|
||||
|
||||
```python
|
||||
PortOption('port', 'port', '0', allow_zero=True)
|
||||
```
|
||||
|
||||
> **_NOTE:_** This option affect the minimal and maximal port number, if "warnings_only" parameter it set to True, it will only emit a warning.
|
||||
|
||||
### allow_wellknown
|
||||
|
||||
The well-known ports (also known as system ports) are those from 1 through 1023. This parameter is set to True by default:
|
||||
|
||||
```python
|
||||
PortOption('port', 'port', '80')
|
||||
```
|
||||
|
||||
```python
|
||||
try:
|
||||
PortOption('port', 'port', '80', allow_wellknown=False)
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
"80" is an invalid port for "port", must be between 1024 and 49151
|
||||
```
|
||||
|
||||
> **_NOTE:_** This option affect the minimal and maximal port number, if "warnings_only" parameter it set to True, it will only emit a warning.
|
||||
|
||||
### allow_registred
|
||||
|
||||
The registered ports are those from 1024 through 49151. This parameter is set to True by default:
|
||||
|
||||
```python
|
||||
PortOption('port', 'port', '1300')
|
||||
```
|
||||
|
||||
```python
|
||||
try:
|
||||
PortOption('port', 'port', '1300', allow_registred=False)
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
"1300" is an invalid port for "port", must be between 1 and 1023
|
||||
```
|
||||
|
||||
> **_NOTE:_** This option affect the minimal and maximal port number, if "warnings_only" parameter it set to True, it will only emit a warning.
|
||||
|
||||
### allow_protocol
|
||||
|
||||
```python
|
||||
PortOption('port', 'port', '1300', allow_protocol="True")
|
||||
PortOption('port', 'port', 'tcp:1300', allow_protocol="True")
|
||||
PortOption('port', 'port', 'udp:1300', allow_protocol="True")
|
||||
```
|
||||
|
||||
### allow_private
|
||||
|
||||
The dynamic or private ports are those from 49152 through 65535. One common use for this range is for ephemeral ports. This parameter is set to False by default:
|
||||
|
||||
```python
|
||||
try:
|
||||
PortOption('port', 'port', '64000')
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
"64000" is an invalid port for "port", must be between 1 and 49151
|
||||
```
|
||||
|
||||
```python
|
||||
PortOption('port', 'port', '64000', allow_private=True)
|
||||
```
|
||||
|
||||
> **_NOTE:_** This option affect the minimal and maximal port number, if "warnings_only" parameter it set to True, it will only emit a warning.
|
||||
|
||||
## Subnetwork option: NetworkOption
|
||||
|
||||
IP networks may be divided into subnetworks:
|
||||
|
||||
```python
|
||||
from tiramisu import NetworkOption
|
||||
NetworkOption('net', 'net', '192.168.0.0')
|
||||
```
|
||||
|
||||
### cidr
|
||||
|
||||
Classless Inter-Domain Routing (CIDR) is a method for allocating IP addresses and IP routing.
|
||||
|
||||
CIDR notation, in which an address or routing prefix is written with a suffix indicating the number of bits of the prefix, such as 192.168.0.0/24:
|
||||
|
||||
```python
|
||||
NetworkOption('net', 'net', '192.168.0.0/24', cidr=True)
|
||||
```
|
||||
|
||||
## Subnetwork mask option: NetmaskOption
|
||||
|
||||
For IPv4, a network may also be characterized by its subnet mask or netmask. This option allow you to enter a netmask:
|
||||
|
||||
```python
|
||||
from tiramisu import NetmaskOption
|
||||
NetmaskOption('mask', 'mask', '255.255.255.0')
|
||||
```
|
||||
|
||||
## Broadcast option: BroadcastOption
|
||||
|
||||
The last address within a network broadcast transmission to all hosts on the link. This option allow you to enter a broadcast:
|
||||
|
||||
```python
|
||||
from tiramisu import BroadcastOption
|
||||
BroadcastOption('bcast', 'bcast', '192.168.0.254')
|
||||
```
|
||||
|
||||
# Internet options
|
||||
|
||||
## Domain name option: DomainnameOption
|
||||
|
||||
Domain names are used in various networking contexts and for application-specific naming and addressing purposes.
|
||||
The DomainnameOption allow different type of domain name:
|
||||
|
||||
```python
|
||||
from tiramisu import DomainnameOption
|
||||
DomainnameOption('domain', 'domain', 'foo.example.net')
|
||||
DomainnameOption('domain', 'domain', 'foo', type='hostname')
|
||||
```
|
||||
|
||||
### type
|
||||
|
||||
There is three type for a domain name:
|
||||
|
||||
- "domainname" (default): lowercase, number, "-" and "." characters are allowed, this must have at least one "."
|
||||
- "hostname": lowercase, number and "-" characters are allowed, the maximum length is 63 characters
|
||||
- "netbios": lowercase, number and "-" characters are allowed, the maximum length is 15 characters
|
||||
|
||||
> **_NOTE:_** If "warnings_only" parameter it set to True, it will raise if length is incorrect by only emit a warning character is not correct.
|
||||
|
||||
### allow_ip
|
||||
|
||||
If the option can contain a domain name or an IP, you can set the "allow_ip" to True, (False by default):
|
||||
|
||||
```python
|
||||
DomainnameOption('domain', 'domain', 'foo.example.net', allow_ip=True)
|
||||
DomainnameOption('domain', 'domain', '192.168.0.1', allow_ip=True)
|
||||
```
|
||||
|
||||
In this case, IP is validate has IPOption would do.
|
||||
|
||||
### allow_cidr_network
|
||||
|
||||
If the option can contain a CIDR network, you can set "allow_cidr_network" to True, (False by default):
|
||||
|
||||
```python
|
||||
DomainnameOption('domain', 'domain', 'foo.example.net', allow_cidr_network=True)
|
||||
DomainnameOption('domain', 'domain', '192.168.0.0/24', allow_cidr_network=True)
|
||||
```
|
||||
|
||||
### allow_without_dot
|
||||
|
||||
A domain name with domainname's type must have a dot. If "allow_without_dot" is True (False by default), we can set a domainname or an hostname:
|
||||
|
||||
```python
|
||||
DomainnameOption('domain', 'domain', 'foo.example.net', allow_without_dot=True)
|
||||
DomainnameOption('domain', 'domain', 'foo', allow_without_dot=True)
|
||||
```
|
||||
|
||||
### allow_startswith_dot
|
||||
|
||||
A domain name with domainname's type mustn't start by a dot. .example.net is not a valid domain. In some case it could be interesting to allow domain name starts by a dot (for ACL in Squid, no proxy option in Firefox, ...):
|
||||
|
||||
```python
|
||||
DomainnameOption('domain', 'domain', 'example.net', allow_startswith_dot=True)
|
||||
DomainnameOption('domain', 'domain', '.example.net', allow_startswith_dot=True)
|
||||
```
|
||||
|
||||
## MAC address option: MACOption
|
||||
|
||||
Validate MAC address:
|
||||
|
||||
```python
|
||||
from tiramisu import MACOption
|
||||
MACOption('mac', 'mac', '01:10:20:20:10:30')
|
||||
```
|
||||
|
||||
## Uniform Resource Locator option: UrlOption
|
||||
|
||||
An Uniform Resource Locator is, in fact, a string starting with http:// or https://, a DomainnameOption, optionaly ':' and a PortOption, and finally filename:
|
||||
|
||||
```python
|
||||
from tiramisu import URLOption
|
||||
URLOption('url', 'url', 'http://foo.example.fr/index.php')
|
||||
URLOption('url', 'url', 'https://foo.example.fr:4200/index.php?login=foo&pass=bar')
|
||||
```
|
||||
|
||||
For parameters, see PortOption and DomainnameOption.
|
||||
|
||||
## Email address option: EmailOption
|
||||
|
||||
Electronic mail (email or e-mail) is a method of exchanging messages ("mail") between people using electronic devices. Here an example:
|
||||
|
||||
```python
|
||||
from tiramisu import EmailOption
|
||||
EmailOption('mail', 'mail', 'foo@example.net')
|
||||
```
|
||||
|
||||
# Unix options
|
||||
|
||||
## Unix username: UsernameOption
|
||||
|
||||
An unix username option is a 32 characters maximum length with lowercase ASCII characters, number, '_' or '-'. The username have to start with lowercase ASCII characters or "_":
|
||||
|
||||
```python
|
||||
from tiramisu import UsernameOption
|
||||
UsernameOption('user', 'user', 'my_user')
|
||||
```
|
||||
|
||||
## Unix groupname: GroupnameOption
|
||||
|
||||
Same condition for unix group name:
|
||||
|
||||
```python
|
||||
from tiramisu import GroupnameOption
|
||||
GroupnameOption('group', 'group', 'my_group')
|
||||
```
|
||||
|
||||
## Password option: PasswordOption
|
||||
|
||||
Simple string with no other restriction:
|
||||
|
||||
```python
|
||||
from tiramisu import PasswordOption
|
||||
PasswordOption('pass', 'pass', 'oP$¨1jiJie')
|
||||
```
|
||||
|
||||
## Unix filename option: FilenameOption
|
||||
|
||||
For this option, only lowercase and uppercas ASCII character, "-", ".", "_", "~", and "/" are allowed:
|
||||
|
||||
```python
|
||||
from tiramisu import FilenameOption
|
||||
FilenameOption('file', 'file', '/etc/tiramisu/tiramisu.conf')
|
||||
```
|
||||
|
||||
## Unix file permissions: PermissionsOption
|
||||
|
||||
Valid the representing Unix permissions is an octal (base-8) notation.
|
||||
|
||||
```python
|
||||
from tiramisu import PermissionsOption
|
||||
PermissionsOption('perms', 'perms', 755)
|
||||
PermissionsOption('perms', 'perms', 1755)
|
||||
```
|
||||
|
||||
This option doesn't allow (or display a warning with warnings_only):
|
||||
|
||||
- 777 (two weak value)
|
||||
- others have more right than group
|
||||
- group has more right than user
|
||||
|
||||
# Date option
|
||||
|
||||
## Date option: DateOption
|
||||
|
||||
Date option waits for a date with format YYYY-MM-DD:
|
||||
|
||||
```python
|
||||
from tiramisu import DateOption
|
||||
DateOption('date', 'date', '2019-10-30')
|
||||
```
|
||||
|
||||
|
|
@ -1,183 +0,0 @@
|
|||
# Create it's own option
|
||||
|
||||
## Generic regexp option: "RegexpOption"
|
||||
|
||||
Use "RegexpOption" to create custom option is very simple.
|
||||
|
||||
You just have to create an object that inherits from "RegexpOption" and that has the following class attributes:
|
||||
|
||||
- \_\_slots\_\_: with new data members (the values should always be "tuple()")
|
||||
- \_type = with a name
|
||||
- \_display_name: with the display name (for example in error message)
|
||||
- \_regexp: with a compiled regexp
|
||||
|
||||
Here an example to an option that only accept string with on lowercase ASCII vowel characters:
|
||||
|
||||
```python
|
||||
import re
|
||||
from tiramisu import RegexpOption
|
||||
class VowelOption(RegexpOption):
|
||||
__slots__ = tuple()
|
||||
_type = 'vowel'
|
||||
_display_name = "string with vowel"
|
||||
_regexp = re.compile(r"^[aeiouy]*$")
|
||||
```
|
||||
|
||||
Let's try our object:
|
||||
|
||||
```python
|
||||
VowelOption('vowel', 'Vowel', 'aae')
|
||||
```
|
||||
|
||||
```python
|
||||
try:
|
||||
VowelOption('vowel', 'Vowel', 'oooups')
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```python
|
||||
"oooups" is an invalid string with vowel for "Vowel"
|
||||
```
|
||||
|
||||
## Create a custom option
|
||||
|
||||
An option always inherits from "Option" object. This object has the following class attributes:
|
||||
|
||||
- \_\_slots\_\_: a tuple with new data members (by default you should set "tuple()")
|
||||
- \_type = with a name
|
||||
- \_display_name: with the display name (for example in error message)
|
||||
|
||||
Here an example to an lipogram option:
|
||||
|
||||
```python
|
||||
from tiramisu import Option
|
||||
from tiramisu.error import ValueWarning
|
||||
import warnings
|
||||
|
||||
class LipogramOption(Option):
|
||||
__slots__ = tuple()
|
||||
_type = 'lipogram'
|
||||
_display_name = 'lipogram'
|
||||
#
|
||||
# First of all we want to add a custom parameter to ask the minimum length (min_len) of the value:
|
||||
def __init__(self,
|
||||
*args,
|
||||
min_len=100,
|
||||
**kwargs):
|
||||
# store extra parameters
|
||||
extra = {'_min_len': min_len}
|
||||
super().__init__(*args,
|
||||
extra=extra,
|
||||
**kwargs)
|
||||
#
|
||||
# We have a first validation method.
|
||||
# In this method, we verify that the value is a string and that there is no "e" on it:
|
||||
# Even if user set warnings_only attribute, this method will raise.
|
||||
def validate(self,
|
||||
value):
|
||||
# first, valid that the value is a string
|
||||
if not isinstance(value, str):
|
||||
raise ValueError('invalid string')
|
||||
# and verify that there is any 'e' in the sentense
|
||||
if 'e' in value:
|
||||
raise ValueError('Perec wrote a book without any "e", you could not do it in a simple sentence?')
|
||||
#
|
||||
# Finally we add a method to valid the value length.
|
||||
# If "warnings_only" is set to True, a warning will be emit:
|
||||
def second_level_validation(self,
|
||||
value,
|
||||
warnings_only):
|
||||
# retrive parameter in extra
|
||||
min_len = self.impl_get_extra('_min_len')
|
||||
# verify the sentense length
|
||||
if len(value) < min_len:
|
||||
# raise message, in this case, warning and error message are different
|
||||
if warnings_only:
|
||||
msg = f'it would be better to have at least {min_len} characters in the sentence'
|
||||
else:
|
||||
msg = f'you must have at least {min_len} characters in the sentence'
|
||||
raise ValueError(msg)
|
||||
```
|
||||
|
||||
Let's test it:
|
||||
|
||||
1. the character "e" is in the value:
|
||||
|
||||
```python
|
||||
try:
|
||||
LipogramOption('lipo',
|
||||
'Lipogram',
|
||||
'I just want to add a quality string that has no bad characters')
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
"I just want to add a quality string that has no bad characters" is an invalid lipogram for "Lipogram", Perec wrote a book without any "e", you could not do it in a simple sentence?
|
||||
```
|
||||
|
||||
2. the character "e" is in the value and warnings_only is set to True:
|
||||
|
||||
```python
|
||||
try:
|
||||
LipogramOption('lipo',
|
||||
'Lipogram',
|
||||
'I just want to add a quality string that has no bad characters',
|
||||
warnings_only=True)
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
```
|
||||
|
||||
```
|
||||
"I just want to add a quality string that has no bad characters" is an invalid lipogram for "Lipogram", Perec wrote a book without any "e", you could not do it in a simple sentence?
|
||||
```
|
||||
|
||||
3. the value is too short
|
||||
|
||||
```python
|
||||
try:
|
||||
LipogramOption('lipo',
|
||||
'Lipogram',
|
||||
'I just want to add a quality string that has no bad symbols')
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
"I just want to add a quality string that has no bad symbols" is an invalid lipogram for "Lipogram", you must have at least 100 characters in the sentence
|
||||
```
|
||||
|
||||
4. the value is too short and warnings_only is set to True:
|
||||
|
||||
```python
|
||||
warnings.simplefilter('always', ValueWarning)
|
||||
with warnings.catch_warnings(record=True) as warn:
|
||||
LipogramOption('lipo',
|
||||
'Lipogram',
|
||||
'I just want to add a quality string that has no bad symbols',
|
||||
warnings_only=True)
|
||||
if warn:
|
||||
print(warn[0].message)
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
attention, "I just want to add a quality string that has no bad symbols" could be an invalid lipogram for "Lipogram", it would be better to have at least 100 characters in the sentence
|
||||
```
|
||||
|
||||
5. set minimum length to 50 characters, the value is valid:
|
||||
|
||||
```python
|
||||
LipogramOption('lipo',
|
||||
'Lipogram',
|
||||
'I just want to add a quality string that has no bad symbols',
|
||||
min_len=50)
|
||||
```
|
109
doc/property.md
|
@ -1,109 +0,0 @@
|
|||
# Property
|
||||
|
||||
## What are properties ?
|
||||
|
||||
The properties are a central element of Tiramisu.
|
||||
|
||||
Properties change the behavior of an option or make it unavailable.
|
||||
|
||||
## Read only and read write
|
||||
|
||||
Config can be in two defaut mode:
|
||||
|
||||
### read_only
|
||||
|
||||
You can get all variables in a "Config" that not disabled.
|
||||
|
||||
Off course, as the Config is read only, you cannot set any value to any option.
|
||||
Only value with :doc`calculation` can change value.
|
||||
You cannot access to mandatory variable without values. Verify that all values is set before change mode.
|
||||
|
||||
### read_write
|
||||
|
||||
You can get all options not disabled and not hidden. You can also set all variables not frozen.
|
||||
|
||||
## Common properties
|
||||
|
||||
### hidden
|
||||
|
||||
Option with this property can only get value in read only mode.
|
||||
This property is used for option that user cannot modifify it's value (for example if it's value is calculated).
|
||||
|
||||
### disabled
|
||||
|
||||
We never can access to option with this property.
|
||||
|
||||
### frozen
|
||||
|
||||
Options with this property cannot be modified.
|
||||
|
||||
## Special option properties
|
||||
|
||||
### mandatory
|
||||
|
||||
You should set value for option with this properties. In read only mode we cannot access to this option if no value is set.
|
||||
|
||||
### empty or notempty
|
||||
|
||||
Only used for multi option that are not a follower.
|
||||
|
||||
Mandatory for a multi means that you cannot add None as a value. But value [] is allowed. This is not permit with "empty" property.
|
||||
|
||||
A multi option has automaticly "empty" property. If you don't want allow empty option, just add "notempty" property when you create the option.
|
||||
|
||||
### unique or notunique
|
||||
|
||||
Only used for multi option that are not a follower.
|
||||
|
||||
Raise ValueError if a value is set twice or more in a multi Option.
|
||||
|
||||
A multi option has automaticly "unique" property. If you want allow duplication in option, just add "notunique" property when you create the option.
|
||||
|
||||
### permissive
|
||||
|
||||
Option with 'permissive' cannot raise PropertiesOptionError for properties set in permissive.
|
||||
|
||||
Config with 'permissive', whole option in this config cannot raise PropertiesOptionError for properties set in permissive.
|
||||
|
||||
## Special Config properties
|
||||
|
||||
### cache
|
||||
|
||||
Enable cache settings and values.
|
||||
|
||||
### expire
|
||||
|
||||
Enable settings and values in cache expire after "expiration_time" (by default 5 seconds).
|
||||
|
||||
### everything_frozen
|
||||
|
||||
Whole options in config are frozen (even if option have not frozen property).
|
||||
|
||||
|
||||
### force_default_on_freeze
|
||||
|
||||
Whole options frozen in config will lost his value and use default values.
|
||||
|
||||
### force_metaconfig_on_freeze
|
||||
|
||||
Whole options frozen in config will lost his value and get MetaConfig values.
|
||||
|
||||
### force_store_value
|
||||
|
||||
All options with this properties will be mark has modified.
|
||||
|
||||
### validator
|
||||
|
||||
Launch validator set by user in option (this property has no effect for option validation and second level validation).
|
||||
|
||||
### warnings
|
||||
|
||||
Display warnings during validation.
|
||||
|
||||
### demoting_error_warning
|
||||
|
||||
All value's errors are convert to warning (ValueErrorWarning).
|
||||
|
||||
## Own properties
|
||||
|
||||
There are no specific instructions for creating a property. Just add a string as property create a new property.
|
|
@ -1,13 +0,0 @@
|
|||
# The symbolic link option: SymLinkOption
|
||||
|
||||
A "SymLinkOption" is an option that actually points to another option.
|
||||
|
||||
Each time we will access to a properties of this options, we will have in return the value of other option.
|
||||
|
||||
Creation a "SymLinkOption" is easy:
|
||||
|
||||
```python
|
||||
from tiramisu import StrOption, SymLinkOption
|
||||
st = StrOption('str', 'str')
|
||||
sym = SymLinkOption('sym', st)
|
||||
```
|
494
doc/validator.md
|
@ -1,494 +0,0 @@
|
|||
# Validator
|
||||
|
||||
## What are validator ?
|
||||
|
||||
Validator is a functionnality that allow you to call a function to determine if an option is valid.
|
||||
|
||||
To define validator we have to use [calculation](calculation.md) object.
|
||||
The function have to raise a "ValueError" object if the value is not valid. It could emit a warning when raises a "ValueWarning".
|
||||
|
||||
## Validator with options
|
||||
|
||||
Here an example, where we want to ask a new password to an user. This password should not be weak. The password will be asked twice and must match.
|
||||
|
||||
First of all, import necessary object:
|
||||
|
||||
```python
|
||||
from asyncio import run
|
||||
import warnings
|
||||
from re import match
|
||||
from tiramisu import StrOption, IntOption, OptionDescription, Config, \
|
||||
Calculation, Params, ParamOption, ParamSelfOption, ParamValue
|
||||
from tiramisu.error import ValueWarning
|
||||
```
|
||||
|
||||
Create a first function to valid that the password is not weak:
|
||||
|
||||
```python
|
||||
def is_password_conform(password):
|
||||
# password must containe at least a number, a lowercase letter, an uppercase letter and a symbol
|
||||
if not match(r'(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*\W)', password):
|
||||
raise ValueError('please choose a stronger password, try a mix of letters, numbers and symbols')
|
||||
```
|
||||
|
||||
Secondly create a function to valid password length. The password must be longer than the value of "min_len" and should be longer than the value of "recommand_len".
|
||||
|
||||
In first case, function raise "ValueError", this value is incorrect.
|
||||
|
||||
In second case, function raise "ValueWarning", the value is valid but discouraged:
|
||||
|
||||
```python
|
||||
def password_correct_len(min_len, recommand_len, password):
|
||||
# password must have at least min_len characters
|
||||
if len(password) < min_len:
|
||||
raise ValueError(f'use {min_len} characters or more for your password')
|
||||
# password should have at least recommand_len characters
|
||||
if len(password) < recommand_len:
|
||||
raise ValueWarning(f'it would be better to use more than {recommand_len} characters for your password')
|
||||
```
|
||||
|
||||
Thirdly create a function that verify that the login name is not a part of password (password "foo2aZ$" if not valid for user "foo"):
|
||||
|
||||
|
||||
```python
|
||||
def user_not_in_password(login, password):
|
||||
if login in password:
|
||||
raise ValueError('the login must not be part of the password')
|
||||
```
|
||||
|
||||
Now we can creation an option to ask user login:
|
||||
|
||||
```python
|
||||
login = StrOption('login', 'Login', properties=('mandatory',))
|
||||
```
|
||||
|
||||
Create a calculation to launch "is_password_conform". This function will be use in a new option and must validate this new option. So we use the object "ParamSelfOption" has parameter to retrieve the value of current option:
|
||||
|
||||
```python
|
||||
calc1 = Calculation(is_password_conform,
|
||||
Params(ParamSelfOption()))
|
||||
```
|
||||
|
||||
Create a second calculation to launch "password_correct_len" function. We want set 8 as "min_len" value and 12 as "recommand_len" value:
|
||||
|
||||
```python
|
||||
calc2 = Calculation(password_correct_len,
|
||||
Params((ParamValue(8),
|
||||
ParamValue(12),
|
||||
ParamSelfOption())))
|
||||
```
|
||||
|
||||
Create a third calculation to launch "user_not_in_password" function. For this function, we use keyword argument. This function normaly raise "ValueError" but in this case we want demoting this error as a simple warning. So we add "warnings_only" parameter:
|
||||
|
||||
```python
|
||||
calc3 = Calculation(user_not_in_password,
|
||||
Params(kwargs={'login': ParamOption(login),
|
||||
'password': ParamSelfOption()}),
|
||||
warnings_only=True)
|
||||
```
|
||||
|
||||
So now we can create first password option that use those calculations:
|
||||
|
||||
```python
|
||||
password1 = StrOption('password1',
|
||||
'Password',
|
||||
properties=('mandatory',),
|
||||
validators=[calc1, calc2, calc3])
|
||||
```
|
||||
|
||||
A new function is created to conform that password1 and password2 match:
|
||||
|
||||
```python
|
||||
def password_match(password1, password2):
|
||||
if password1 != password2:
|
||||
raise ValueError("those passwords didn't match, try again")
|
||||
```
|
||||
|
||||
And now we can create second password option that use this function:
|
||||
|
||||
```python
|
||||
password2 = StrOption('password2',
|
||||
'Confirm',
|
||||
properties=('mandatory',),
|
||||
validators=[Calculation(password_match, Params((ParamOption(password1), ParamSelfOption())))])
|
||||
```
|
||||
|
||||
Finally we create optiondescription and config:
|
||||
|
||||
```python
|
||||
od = OptionDescription('password', 'Define your password', [password1, password2])
|
||||
root = OptionDescription('root', '', [login, od])
|
||||
async def main():
|
||||
config = await Config(root)
|
||||
await config.property.read_write()
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
Now we can test this "Config" with a tested password too weak:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
config = await Config(root)
|
||||
await config.property.read_write()
|
||||
await config.option('login').value.set('user')
|
||||
try:
|
||||
await config.option('password.password1').value.set('aAbBc')
|
||||
except ValueError as err:
|
||||
print(f'Error: {err}')
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
Error: "aAbBc" is an invalid string for "Password", please choose a stronger password, try a mix of letters, numbers and symbols
|
||||
```
|
||||
|
||||
The password is part of error message. In this case it's a bad idea. So we have to remove "prefix" to the error message:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
config = await Config(root)
|
||||
await config.property.read_write()
|
||||
await config.option('login').value.set('user')
|
||||
try:
|
||||
await config.option('password.password1').value.set('aAbBc')
|
||||
except ValueError as err:
|
||||
err.prefix = ''
|
||||
print(f'Error: {err}')
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
Now the error is:
|
||||
|
||||
```
|
||||
Error: please choose a stronger password, try a mix of letters, numbers and symbols
|
||||
```
|
||||
|
||||
Let's try with a password not weak but too short:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
config = await Config(root)
|
||||
await config.property.read_write()
|
||||
await config.option('login').value.set('user')
|
||||
try:
|
||||
await config.option('password.password1').value.set('aZ$1')
|
||||
except ValueError as err:
|
||||
err.prefix = ''
|
||||
print(f'Error: {err}')
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
The error is:
|
||||
|
||||
```
|
||||
Error: use 8 characters or more for your password
|
||||
```
|
||||
|
||||
Now try a password with 8 characters:
|
||||
|
||||
```python
|
||||
warnings.simplefilter('always', ValueWarning)
|
||||
async def main():
|
||||
config = await Config(root)
|
||||
await config.property.read_write()
|
||||
await config.option('login').value.set('user')
|
||||
with warnings.catch_warnings(record=True) as warn:
|
||||
await config.option('password.password1').value.set('aZ$1bN:2')
|
||||
if warn:
|
||||
warn[0].message.prefix = ''
|
||||
print(f'Warning: {warn[0].message}')
|
||||
password = await config.option('password.password1').value.get()
|
||||
print(f'The password is "{password}"')
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
Warning is display but password is store:
|
||||
|
||||
```
|
||||
Warning: it would be better to use more than 12 characters for your password
|
||||
The password is "aZ$1bN:2"
|
||||
```
|
||||
|
||||
Try a password with the login as part of it:
|
||||
|
||||
```python
|
||||
warnings.simplefilter('always', ValueWarning)
|
||||
async def main():
|
||||
config = await Config(root)
|
||||
await config.property.read_write()
|
||||
await config.option('login').value.set('user')
|
||||
with warnings.catch_warnings(record=True) as warn:
|
||||
await config.option('password.password1').value.set('aZ$1bN:2u@1Bjuser')
|
||||
if warn:
|
||||
warn[0].message.prefix = ''
|
||||
print(f'Warning: {warn[0].message}')
|
||||
password = await config.option('password.password1').value.get()
|
||||
print(f'The password is "{password}"')
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
Warning is display but password is store:
|
||||
|
||||
```
|
||||
Warning: the login must not be part of the password
|
||||
The password is "aZ$1bN:2u@1Bjuser"
|
||||
```
|
||||
|
||||
Now try with a valid password but that doesn't match:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
config = await Config(root)
|
||||
await config.property.read_write()
|
||||
await config.option('login').value.set('user')
|
||||
await config.option('password.password1').value.set('aZ$1bN:2u@1Bj')
|
||||
try:
|
||||
await config.option('password.password2').value.set('aZ$1aaaa')
|
||||
except ValueError as err:
|
||||
err.prefix = ''
|
||||
print(f'Error: {err}')
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
An error is displayed:
|
||||
|
||||
```
|
||||
Error: those passwords didn't match, try again
|
||||
```
|
||||
|
||||
Finally try a valid password:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
config = await Config(root)
|
||||
await config.property.read_write()
|
||||
await config.option('login').value.set('user')
|
||||
await config.option('login').value.set('user')
|
||||
await config.option('password.password1').value.set('aZ$1bN:2u@1Bj')
|
||||
await config.option('password.password2').value.set('aZ$1bN:2u@1Bj')
|
||||
await config.property.read_only()
|
||||
user_login = await config.option('login').value.get()
|
||||
password = await config.option('password.password2').value.get()
|
||||
print(f'The password for "{user_login}" is "{password}"')
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
As expected, we have:
|
||||
|
||||
```
|
||||
The password for "user" is "aZ$1bN:2u@1Bj"
|
||||
```
|
||||
|
||||
## Validator with a multi option
|
||||
|
||||
Assume we ask percentage value to an user. The sum of values mustn't be higher than 100% and shouldn't be lower than 100%.
|
||||
|
||||
Let's start by importing the objects:
|
||||
|
||||
```python
|
||||
from asyncio import run
|
||||
import warnings
|
||||
from tiramisu import IntOption, OptionDescription, Config, \
|
||||
Calculation, Params, ParamSelfOption
|
||||
from tiramisu.error import ValueWarning
|
||||
```
|
||||
|
||||
Continue by writing the validation function:
|
||||
|
||||
```python
|
||||
def valid_pourcent(option):
|
||||
total = sum(option)
|
||||
if total > 100:
|
||||
raise ValueError(f'the total {total}% is bigger than 100%')
|
||||
if total < 100:
|
||||
raise ValueWarning(f'the total {total}% is lower than 100%')
|
||||
```
|
||||
|
||||
And create a simple option in a single OptionDescription:
|
||||
|
||||
```python
|
||||
percent = IntOption('percent',
|
||||
'Percent',
|
||||
multi=True,
|
||||
validators=[Calculation(valid_pourcent, Params(ParamSelfOption()))])
|
||||
od = OptionDescription('root', 'root', [percent])
|
||||
```
|
||||
|
||||
Now try with bigger sum:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
config = await Config(od)
|
||||
try:
|
||||
await config.option('percent').value.set([20, 90])
|
||||
except ValueError as err:
|
||||
err.prefix = ''
|
||||
print(f'Error: {err}')
|
||||
percent_value = await config.option('percent').value.get()
|
||||
print(f'The value is "{percent_value}"')
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
The result is:
|
||||
|
||||
```
|
||||
Error: the total 110% is bigger than 100%
|
||||
The value is "[]"
|
||||
```
|
||||
|
||||
Let's try with lower sum:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
config = await Config(od)
|
||||
warnings.simplefilter('always', ValueWarning)
|
||||
with warnings.catch_warnings(record=True) as warn:
|
||||
await config.option('percent').value.set([20, 70])
|
||||
if warn:
|
||||
warn[0].message.prefix = ''
|
||||
print(f'Warning: {warn[0].message}')
|
||||
percent_value = await config.option('percent').value.get()
|
||||
print(f'The value is "{percent_value}"')
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
The result is:
|
||||
|
||||
```
|
||||
Warning: the total 90% is lower than 100%
|
||||
The value is "[20, 70]"
|
||||
```
|
||||
|
||||
Finally with correct value:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
config = await Config(od)
|
||||
await config.option('percent').value.set([20, 80])
|
||||
percent_value = await config.option('percent').value.get()
|
||||
print(f'The value is "{percent_value}"')
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
The result is:
|
||||
|
||||
```
|
||||
The value is "[20, 80]"
|
||||
```
|
||||
|
||||
## Validator with a follower option
|
||||
|
||||
Assume we want distribute something to differents users. The sum of values mustn't be higher than 100%.
|
||||
|
||||
First, import all needed objects:
|
||||
|
||||
```python
|
||||
from asyncio import run
|
||||
import warnings
|
||||
from tiramisu import StrOption, IntOption, Leadership, OptionDescription, Config, \
|
||||
Calculation, Params, ParamSelfOption, ParamIndex
|
||||
from tiramisu.error import ValueWarning
|
||||
```
|
||||
|
||||
Let's start to write a function with three arguments:
|
||||
|
||||
- the first argument will have all values set for the follower
|
||||
- the second argument will have only last value set for the follower
|
||||
- the third argument will have the index
|
||||
|
||||
```python
|
||||
def valid_pourcent(option, current_option, index):
|
||||
if None in option:
|
||||
return
|
||||
total = sum(option)
|
||||
if total > 100:
|
||||
raise ValueError(f'the value {current_option} (at index {index}) is too big, the total is {total}%')
|
||||
```
|
||||
|
||||
Continue by creating a calculation:
|
||||
|
||||
```python
|
||||
calculation = Calculation(valid_pourcent, Params((ParamSelfOption(whole=True),
|
||||
ParamSelfOption(),
|
||||
ParamIndex())))
|
||||
```
|
||||
|
||||
And instanciate differents option:
|
||||
|
||||
```python
|
||||
user = StrOption('user', 'User', multi=True)
|
||||
percent = IntOption('percent',
|
||||
'Distribution',
|
||||
multi=True,
|
||||
validators=[calculation])
|
||||
leader = Leadership('percent', 'Percent', [user, percent])
|
||||
od = OptionDescription('root', 'root', [leader])
|
||||
```
|
||||
|
||||
Add two values to the leader:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
config = await Config(od)
|
||||
await config.option('percent.user').value.set(['user1', 'user2'])
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
The user user1 will have 20%:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
config = await Config(od)
|
||||
await config.option('percent.user').value.set(['user1', 'user2'])
|
||||
await config.option('percent.percent', 0).value.set(20)
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
If we try to set 90% to user2:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
config = await Config(od)
|
||||
await config.option('percent.user').value.set(['user1', 'user2'])
|
||||
await config.option('percent.percent', 0).value.set(20)
|
||||
try:
|
||||
await config.option('percent.percent', 1).value.set(90)
|
||||
except ValueError as err:
|
||||
err.prefix = ''
|
||||
print(f'Error: {err}')
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
results:
|
||||
|
||||
```
|
||||
Error: the value 90 (at index 1) is too big, the total is 110%
|
||||
```
|
||||
|
||||
No problem with 80%:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
config = await Config(od)
|
||||
await config.option('percent.user').value.set(['user1', 'user2'])
|
||||
await config.option('percent.percent', 0).value.set(20)
|
||||
await config.option('percent.percent', 1).value.set(80)
|
||||
|
||||
run(main())
|
||||
```
|
23
docs/Makefile
Normal file
|
@ -0,0 +1,23 @@
|
|||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
SPHINXPROJ = pyfun
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
mkdir -p _build/html/_modules
|
||||
make -C ../src all
|
||||
|
4
docs/_static/css/custom.css
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
.wy-table-responsive table td {
|
||||
white-space: normal;
|
||||
}
|
||||
|
BIN
docs/_static/python-logo-large.png
vendored
Normal file
After Width: | Height: | Size: 13 KiB |
93
docs/api_global_permissives.rst
Normal file
|
@ -0,0 +1,93 @@
|
|||
==================
|
||||
Global permissive
|
||||
==================
|
||||
|
||||
Permissives allow access, during a calculation, to a normally unavailable variable.
|
||||
|
||||
In the :doc:api_property example we add a new `create` option that has a calculation with the option `exists` as parameter.
|
||||
|
||||
This option has a calculated default_multi value. If the file exists (so `exists` option is True) we don't want create automaticly the file:
|
||||
|
||||
.. literalinclude:: src/api_global_permissive.py
|
||||
:lines: 7-8
|
||||
:linenos:
|
||||
|
||||
Here is the new option:
|
||||
|
||||
.. literalinclude:: src/api_global_permissive.py
|
||||
:lines: 20-23
|
||||
:linenos:
|
||||
|
||||
:download:`download the config <src/api_global_permissive.py>`
|
||||
|
||||
Get/add/pop/reset global permissive
|
||||
=====================================
|
||||
|
||||
Let's try this config:
|
||||
|
||||
>>> config.property.read_write()
|
||||
>>> config.option('new.filename').value.set(['/etc', '/unknown'])
|
||||
>>> config.value.get()
|
||||
{'new.filename': ['/etc', '/unknown'], 'new.exists': [True, False], 'new.create': [False, True]}
|
||||
|
||||
Now we want to see `advanced` option. But how calculate create value?
|
||||
|
||||
>>> config.property.add('advanced')
|
||||
>>> try:
|
||||
... config.value.get()
|
||||
... except ConfigError as err:
|
||||
... print(err)
|
||||
unable to carry out a calculation for "Create automaticly the file", cannot access to option "This file exists" because has property "advanced"
|
||||
|
||||
We just have to add `advanced` permissive to allow calculation:
|
||||
|
||||
>>> config.permissive.add('advanced')
|
||||
>>> config.value.get()
|
||||
{'new.filename': ['/etc', '/unknown'], 'new.create': [False, True]}
|
||||
|
||||
At any time we can retrieve all global permissive:
|
||||
|
||||
>>> config.permissive.get()
|
||||
frozenset({'hidden', 'advanced'})
|
||||
|
||||
We can remove on permissive:
|
||||
|
||||
>>> config.permissive.pop('hidden')
|
||||
>>> config.permissive.get()
|
||||
frozenset({'advanced'})
|
||||
|
||||
And finally we can reset all permissives:
|
||||
|
||||
>>> config.permissive.reset()
|
||||
>>> config.permissive.get()
|
||||
frozenset()
|
||||
|
||||
Default permissives
|
||||
============================
|
||||
|
||||
Tiramisu estimate default permissive.
|
||||
|
||||
All properties added in `read write` mode and removed in `read only` mode are, by default, included in permissive list when we change mode:
|
||||
|
||||
>>> default = config.property.getdefault('read_write', 'append')
|
||||
>>> config.property.setdefault(frozenset(default | {'advanced'}), 'read_write', 'append')
|
||||
>>> default = config.property.getdefault('read_only', 'remove')
|
||||
>>> config.property.setdefault(frozenset(default | {'advanced'}), 'read_only', 'remove')
|
||||
>>> config.property.read_write()
|
||||
>>> config.permissive.get()
|
||||
frozenset({'advanced', 'hidden'})
|
||||
|
||||
Importation and exportation
|
||||
================================
|
||||
|
||||
In config, all permissive (global's and option's permissives) can be exportated:
|
||||
|
||||
>>> config.permissive.exportation()
|
||||
{None: frozenset({'hidden', 'advanced'})}
|
||||
|
||||
And reimported later:
|
||||
|
||||
>>> export = config.permissive.exportation()
|
||||
>>> config.permissive.importation(export)
|
||||
|
||||
.. note:: The exportation format is not stable and can be change later, please do not use importation otherwise than jointly with exportation.
|
185
docs/api_global_properties.rst
Normal file
|
@ -0,0 +1,185 @@
|
|||
================
|
||||
Global property
|
||||
================
|
||||
|
||||
Before start, have a look to :doc:property.
|
||||
|
||||
Let's start by import needed objects:
|
||||
|
||||
.. literalinclude:: src/api_global_property.py
|
||||
:lines: 1-4
|
||||
:linenos:
|
||||
|
||||
Instanciate a first option with `mandatory` property:
|
||||
|
||||
.. literalinclude:: src/api_global_property.py
|
||||
:lines: 7-10
|
||||
:linenos:
|
||||
|
||||
Instanciate a second option with calculated value, which verify if the file exists.
|
||||
|
||||
This options has `frozen`, `force_default_on_freeze` and `advanced` properties:
|
||||
|
||||
.. literalinclude:: src/api_global_property.py
|
||||
:lines: 11-15
|
||||
:linenos:
|
||||
|
||||
This two options are in a leadership:
|
||||
|
||||
.. literalinclude:: src/api_global_property.py
|
||||
:lines: 16-18
|
||||
:linenos:
|
||||
|
||||
Finally create the root option description and the config:
|
||||
|
||||
.. literalinclude:: src/api_global_property.py
|
||||
:lines: 19-20
|
||||
:linenos:
|
||||
|
||||
:download:`download the config <src/api_global_property.py>`
|
||||
|
||||
Read only and read write
|
||||
==========================
|
||||
|
||||
By default, there is no restriction.
|
||||
|
||||
For example, it's possible to change value of a `frozen` option (here the `exists`' option):
|
||||
|
||||
>>> config.option('new.filename').value.set(['/etc'])
|
||||
>>> config.option('new.exists', 0).value.set(False)
|
||||
|
||||
To have the good properties in "read / write" mode:
|
||||
|
||||
>>> config.property.read_write()
|
||||
>>> config.option('new.filename').value.set(['/etc'])
|
||||
>>> try:
|
||||
... config.option('new.exists', 0).value.set(False)
|
||||
...except PropertiesOptionError as err:
|
||||
... print(err)
|
||||
cannot modify the option "This file exists" because has property "frozen"
|
||||
|
||||
The read write mode is used be a human who wants to modify the configuration.
|
||||
Some variables are not displayed, because this person cannot modified it.
|
||||
|
||||
To have the good properties in "read only" mode:
|
||||
|
||||
>>> config.property.read_only()
|
||||
>>> try:
|
||||
... config.option('new.filename').value.set(['/etc'])
|
||||
...except PropertiesOptionError as err:
|
||||
... print(err)
|
||||
cannot modify the option "Filename" because has property "frozen"
|
||||
|
||||
In this mode it is impossible to modify the values of the options.
|
||||
It should be use by a script, for build a template, ...
|
||||
All variables not desactived are accessible.
|
||||
|
||||
Get/add/pop/reset global property
|
||||
=======================================
|
||||
|
||||
The default read only and read write properties are:
|
||||
|
||||
>>> config.property.read_only()
|
||||
>>> config.property.get()
|
||||
frozenset({'force_store_value', 'validator', 'everything_frozen', 'warnings', 'cache', 'mandatory', 'frozen', 'empty', 'disabled'})
|
||||
>>> config.property.read_write()
|
||||
>>> config.property.get()
|
||||
frozenset({'frozen', 'cache', 'warnings', 'disabled', 'validator', 'force_store_value', 'hidden'})
|
||||
|
||||
In the current config, the option has property `advanced`.
|
||||
|
||||
Has you can see below, the `advanced` is not used in any mode. This property doesn't affect Tiramisu.
|
||||
|
||||
Imagine that you don't want to see any advanced option by default. Just add this property in global property:
|
||||
|
||||
>>> config.option('new.filename').value.set(['/etc'])
|
||||
>>> config.property.read_write()
|
||||
>>> config.value.get()
|
||||
{'new.filename': ['/etc'], 'new.exists': [True]}
|
||||
>>> config.property.add('advanced')
|
||||
>>> config.property.get()
|
||||
frozenset({'frozen', 'advanced', 'hidden', 'validator', 'force_store_value', 'disabled', 'cache', 'warnings'})
|
||||
>>> config.value.get()
|
||||
{'new.filename': ['/etc']}
|
||||
|
||||
Of course you want to access to this option in read only mode.
|
||||
So you have to remove this property:
|
||||
|
||||
>>> config.property.read_only()
|
||||
>>> config.property.pop('advanced')
|
||||
>>> config.property.get()
|
||||
frozenset({'force_store_value', 'everything_frozen', 'frozen', 'warnings', 'empty', 'disabled', 'mandatory', 'cache', 'validator'})
|
||||
>>> config.value.get()
|
||||
{'new.filename': ['/etc'], 'new.exists': [True]}
|
||||
|
||||
At any time we can return to the default property (default means initialized properties, before change to read only or read write mode):
|
||||
|
||||
>>> config.property.read_only()
|
||||
>>> config.property.get()
|
||||
frozenset({'empty', 'cache', 'force_store_value', 'everything_frozen', 'warnings', 'frozen', 'disabled', 'mandatory', 'validator'})
|
||||
>>> config.property.reset()
|
||||
>>> config.property.get()
|
||||
frozenset({'cache', 'warnings', 'validator'})
|
||||
|
||||
Get default properties in mode
|
||||
=======================================
|
||||
|
||||
Add or pop properties each time we pass from one mode to an other is not a good idea. It better to change `read_write` and `read_only` mode directly.
|
||||
|
||||
Change mode means, in fact, add some properties and remove some other properties.
|
||||
|
||||
For example, when we pass to read_write mode, this properties are added:
|
||||
|
||||
>>> config.property.getdefault('read_write', 'append')
|
||||
frozenset({'disabled', 'validator', 'force_store_value', 'hidden', 'frozen'})
|
||||
|
||||
and this properties are remove:
|
||||
|
||||
>>> config.property.getdefault('read_write', 'remove')
|
||||
frozenset({'empty', 'everything_frozen', 'mandatory', 'permissive'})
|
||||
|
||||
Here is properties added when pass to read_only mode:
|
||||
|
||||
>>> config.property.getdefault('read_only', 'append')
|
||||
frozenset({'empty', 'mandatory', 'validator', 'disabled', 'force_store_value', 'everything_frozen', 'frozen'})
|
||||
|
||||
and this properties are remove:
|
||||
|
||||
>>> config.property.getdefault('read_only', 'remove')
|
||||
frozenset({'hidden', 'permissive'})
|
||||
|
||||
Just add the property to the default value to automatically automate the addition and deletion.
|
||||
We want to add the property when we switch to "read write" mode and automatically delete this property when we switch to "read only" mode:
|
||||
|
||||
>>> default = config.property.getdefault('read_write', 'append')
|
||||
>>> config.property.setdefault(frozenset(default | {'advanced'}), 'read_write', 'append')
|
||||
>>> default = config.property.getdefault('read_only', 'remove')
|
||||
>>> config.property.setdefault(frozenset(default | {'advanced'}), 'read_only', 'remove')
|
||||
|
||||
Let's try:
|
||||
|
||||
>>> 'advanced' in config.property.get()
|
||||
False
|
||||
>>> config.property.read_write()
|
||||
>>> 'advanced' in config.property.get()
|
||||
True
|
||||
>>> config.property.read_only()
|
||||
>>> 'advanced' in config.property.get()
|
||||
False
|
||||
|
||||
Importation and exportation
|
||||
=======================================
|
||||
|
||||
In config, all properties (global's and option's properties) can be exportated:
|
||||
|
||||
>>> config.property.exportation()
|
||||
{None: frozenset({'empty', 'cache', 'warnings', 'validator', 'disabled', 'force_store_value', 'everything_frozen', 'frozen', 'mandatory'})}
|
||||
|
||||
And reimported later:
|
||||
|
||||
>>> export = config.property.exportation()
|
||||
>>> config.property.importation(export)
|
||||
|
||||
.. note:: The exportation format is not stable and can be change later, please do not use importation otherwise than jointly with exportation.
|
||||
|
||||
|
17
docs/api_option_permissive.rst
Normal file
|
@ -0,0 +1,17 @@
|
|||
=====================
|
||||
Option's permissive
|
||||
=====================
|
||||
|
||||
|
||||
.. FIXME advanced in permissive
|
||||
|
||||
|
||||
.. FIXME unrestraint, forcepermissive
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
403
docs/api_option_property.rst
Normal file
|
@ -0,0 +1,403 @@
|
|||
==================
|
||||
Option's property
|
||||
==================
|
||||
|
||||
Let's start to build a config.
|
||||
|
||||
First of, import needed object:
|
||||
|
||||
.. literalinclude:: src/api_option_property.py
|
||||
:lines: 1-9
|
||||
:linenos:
|
||||
|
||||
Instanciate a first option to call a file name:
|
||||
|
||||
.. literalinclude:: src/api_option_property.py
|
||||
:lines: 56-59
|
||||
:linenos:
|
||||
|
||||
Secondly add an `exists` option to know if this file is already created:
|
||||
|
||||
.. literalinclude:: src/api_option_property.py
|
||||
:lines: 60-64
|
||||
:linenos:
|
||||
|
||||
Thirdly add a `create` option used by a potential script to create wanted file:
|
||||
|
||||
.. literalinclude:: src/api_option_property.py
|
||||
:lines: 65-72
|
||||
:linenos:
|
||||
|
||||
A new option is create to known the file type. If file already exists, retrieve automaticly the type, otherwise ask to the user:
|
||||
|
||||
.. literalinclude:: src/api_option_property.py
|
||||
:lines: 35-42
|
||||
:linenos:
|
||||
|
||||
.. literalinclude:: src/api_option_property.py
|
||||
:lines: 73-87
|
||||
:linenos:
|
||||
|
||||
In same model, create a `user` and `group` name options:
|
||||
|
||||
.. literalinclude:: src/api_option_property.py
|
||||
:lines: 12-32
|
||||
:linenos:
|
||||
|
||||
.. literalinclude:: src/api_option_property.py
|
||||
:lines: 88-111
|
||||
:linenos:
|
||||
|
||||
Finally create a `mode` option:
|
||||
|
||||
.. literalinclude:: src/api_option_property.py
|
||||
:lines: 45-53
|
||||
:linenos:
|
||||
|
||||
.. literalinclude:: src/api_option_property.py
|
||||
:lines: 112-116
|
||||
:linenos:
|
||||
|
||||
Let's build the config:
|
||||
|
||||
.. literalinclude:: src/api_option_property.py
|
||||
:lines: 118-124
|
||||
:linenos:
|
||||
|
||||
:download:`download the config <src/api_option_property.py>`
|
||||
|
||||
Get/add/pop/reset property
|
||||
=================================
|
||||
|
||||
option description's property
|
||||
'''''''''''''''''''''''''''''''''''''''''
|
||||
|
||||
An option description is an option. It's possible to set property to it:
|
||||
|
||||
>>> config.property.read_write()
|
||||
>>> config.option('new').property.get()
|
||||
set()
|
||||
|
||||
To add a property:
|
||||
|
||||
>>> config.option('new').property.add('disabled')
|
||||
>>> config.option('new').property.get()
|
||||
set('disabled')
|
||||
|
||||
The property affect the option description:
|
||||
|
||||
>>> try:
|
||||
... config.option('new').value.get()
|
||||
... except PropertiesOptionError as err:
|
||||
... print(err)
|
||||
cannot access to optiondescription "Add new file" because has property "disabled"
|
||||
|
||||
But, of course the child option too. If access to option description is not possible, it's not possible to child option too:
|
||||
|
||||
>>> try:
|
||||
... config.option('new.filename').value.get()
|
||||
... except PropertiesOptionError as err:
|
||||
... print(err)
|
||||
cannot access to optiondescription "Add new file" because has property "disabled"
|
||||
|
||||
We can remove an existed property too:
|
||||
|
||||
>>> config.option('new').property.add('hidden')
|
||||
>>> config.option('new').property.get()
|
||||
{'hidden', 'disabled'}
|
||||
>>> config.option('new').property.pop('hidden')
|
||||
>>> config.option('new').property.get()
|
||||
{'disabled'}
|
||||
|
||||
|
||||
It's possible to reset property:
|
||||
|
||||
>>> config.option('new').property.reset()
|
||||
>>> config.option('new').value.get()
|
||||
{'filename': [], 'exists': [], 'create': [], 'type': [], 'user': [], 'group': [], 'mode': []}
|
||||
|
||||
option's property
|
||||
'''''''''''''''''''''''''''''''''''''''
|
||||
|
||||
In a simple option we can add, pop or reset property:
|
||||
|
||||
>>> config.property.read_write()
|
||||
>>> config.option('new.filename').property.get())
|
||||
{'mandatory', 'unique', 'empty'}
|
||||
>>> config.option('new.filename').property.add('frozen')
|
||||
>>> config.option('new.filename').property.get()
|
||||
{'mandatory', 'unique', 'frozen', 'empty'}
|
||||
>>> config.option('new.filename').property.pop('empty')
|
||||
>>> config.option('new.filename').property.get()
|
||||
{'frozen', 'mandatory', 'unique'}
|
||||
>>> config.option('new.filename').property.reset()
|
||||
>>> config.option('new.filename').property.get()
|
||||
{'mandatory', 'unique', 'empty'}
|
||||
|
||||
leader's property
|
||||
''''''''''''''''''''''''''''
|
||||
|
||||
In leader's option can only have a list of property. For other's property, please set directly in leadership option:
|
||||
|
||||
>>> config.property.read_write()
|
||||
>>> try:
|
||||
... config.option('new.filename').property.add('hidden')
|
||||
... except LeadershipError as err:
|
||||
... print(err)
|
||||
leader cannot have "hidden" property
|
||||
>>> config.option('new').property.add('hidden')
|
||||
|
||||
This `hidden` property has to affect leader option but also all follower option.
|
||||
That why you have to set this kind of properties directly in leadership option.
|
||||
|
||||
.. note::
|
||||
Allowed properties for a leader: 'empty', 'unique', 'force_store_value', 'mandatory', 'force_default_on_freeze', 'force_metaconfig_on_freeze', and 'frozen'.
|
||||
|
||||
|
||||
follower's property
|
||||
'''''''''''''''''''''''''''''''''''''''
|
||||
|
||||
First of add, add values in leader option:
|
||||
|
||||
>>> config.property.read_write()
|
||||
>>> config.option('new.filename').value.set(['/etc/passwd', 'unknown1', 'unknown2'])
|
||||
|
||||
We have to get property with an index:
|
||||
|
||||
>>> config.option('new.create', 1).property.get()
|
||||
set()
|
||||
|
||||
We can set property with index:
|
||||
|
||||
>>> config.option('new.create', 1).property.add('frozen')
|
||||
>>> config.option('new.create', 1).property.get()
|
||||
{'frozen'}
|
||||
>>> config.option('new.create', 2).property.get()
|
||||
set()
|
||||
|
||||
But we can alse set without index (available for all follower's value):
|
||||
|
||||
>>> config.option('new.create').property.add('frozen')
|
||||
>>> print(config.option('new.create', 1).property.get())
|
||||
{'frozen'}
|
||||
>>> print(config.option('new.create', 2).property.get())
|
||||
{'frozen'}
|
||||
|
||||
Calculated property
|
||||
=======================
|
||||
|
||||
A property can be a :doc:`calculation`. That means that the property will be set or not following the context.
|
||||
|
||||
The Calculation can return two type of value:
|
||||
|
||||
- a `str` this string is a new property
|
||||
- `None` so this property is cancel
|
||||
|
||||
First of all, have a look to the `create` properties:
|
||||
|
||||
.. literalinclude:: src/api_option_property.py
|
||||
:lines: 68-72
|
||||
:linenos:
|
||||
|
||||
This option has only one property which is `disabled` when `exists` has value True.
|
||||
|
||||
If the file exists, we don't have to now if user wants create it. It is already exists. So we don't have to access to this option.
|
||||
|
||||
Secondly, have a look to the `type` properties:
|
||||
|
||||
.. literalinclude:: src/api_option_property.py
|
||||
:lines: 79-87
|
||||
:linenos:
|
||||
|
||||
There is:
|
||||
|
||||
- two static properties: `force_default_on_freeze` and `mandatory`.
|
||||
- two calculated properties: `hidden` and `frozen`
|
||||
|
||||
If the file is already exists, the two calculated properties are present to this option.
|
||||
|
||||
So we can access to this option only in read only mode and user cannot modified it's value.
|
||||
|
||||
Finally have a look to the `username` and `grpname` options' properties:
|
||||
|
||||
.. literalinclude:: src/api_option_property.py
|
||||
:lines: 94-99
|
||||
:linenos:
|
||||
|
||||
In this case we have two properties:
|
||||
|
||||
- one static property: `force_store_value`
|
||||
- one calculated property: `mandatory`
|
||||
|
||||
This calculated property is apply only if `create` is True.
|
||||
|
||||
Be carefull to the `create` option. It could be disabled, so not accessible in calculation if the file exists as see previously.
|
||||
|
||||
That why we add notraisepropertyerror attribute to True, even if the calculation will failed.
|
||||
In this case the value of `create` is not add in `calc_value` argument.
|
||||
|
||||
In this case the function `calc_value` consider that the property `mandatory` has to be set.
|
||||
|
||||
But we just want to set `mandatory` property only if create is False. That why we add the no_condition_is_invalid to True.
|
||||
|
||||
Force the registration of a value
|
||||
====================================
|
||||
|
||||
The property `force_store_value` is a special property. This property permit to store a value automaticly even if user do not set value or reset the value.
|
||||
This is useful especially, for example, for recording a random draw password through a calculation. Or to store any first result for a calculation.
|
||||
|
||||
To the, create a new config:
|
||||
|
||||
>>> config = Config(root)
|
||||
|
||||
If we add value in `filename`, the option `exists` stay a default value, but not the `mode` option, which has `force_store_value`:
|
||||
|
||||
>>> config.property.read_write()
|
||||
>>> config.option('new.filename').value.set(['/etc'])
|
||||
>>> print(config.option('new.filename').owner.get())
|
||||
user
|
||||
>>> print(config.option('new.exists', 0).owner.get())
|
||||
default
|
||||
>>> print(config.option('new.mode', 0).owner.get())
|
||||
forced
|
||||
|
||||
If we try to reset `mode` value, this option is modified:
|
||||
|
||||
>>> config.option('new.mode', 0).value.reset()
|
||||
>>> config.option('new.mode', 0).owner.get()
|
||||
forced
|
||||
|
||||
Non-empty value, mandatory and unique
|
||||
========================================================
|
||||
|
||||
Leader and multi have automaticly two properties `unique` and `empty`:
|
||||
|
||||
>>> config = Config(OptionDescription('root', 'root', [FilenameOption('filename',
|
||||
... 'Filename',
|
||||
... multi=True)]))
|
||||
>>> config.option('filename').property.get()
|
||||
{'empty', 'unique'}
|
||||
|
||||
To remove `empty` property
|
||||
|
||||
>>> config = Config(OptionDescription('root', 'root', [FilenameOption('filename',
|
||||
... 'Filename',
|
||||
... properties=('notempty',),
|
||||
... multi=True)]))
|
||||
>>> config.option('filename').property.get()
|
||||
{'unique'}
|
||||
>>> config = Config(OptionDescription('root', 'root', [FilenameOption('filename',
|
||||
... 'Filename',
|
||||
... properties=('notunique',),
|
||||
... multi=True)]))
|
||||
>>> config.option('filename').property.get()
|
||||
{'empty'}
|
||||
|
||||
Let's try with previous config.
|
||||
|
||||
First of all we remove `force_store_value` mode:
|
||||
|
||||
>>> config = Config(root)
|
||||
>>> properties = config.property.getdefault('read_write', 'append') - {'force_store_value'}
|
||||
>>> config.property.setdefault(frozenset(properties), 'read_write', 'append')
|
||||
>>> properties = config.property.getdefault('read_only', 'append') - {'force_store_value'}
|
||||
>>> config.property.setdefault(frozenset(properties), 'read_only', 'append')
|
||||
|
||||
In addition to the specified `mandatory` property, leader have automaticly two properties: `unique` and `empty`:
|
||||
|
||||
>>> config.option('new.filename').property.get()
|
||||
{'unique', 'mandatory', 'empty'}
|
||||
|
||||
What is the difference between the property `unique` and `mandatory`?
|
||||
|
||||
Let's try with no value at all:
|
||||
|
||||
>>> config.property.read_only()
|
||||
>>> try:
|
||||
... config.option('new.filename').value.get()
|
||||
>>> except PropertiesOptionError as err:
|
||||
... print(err)
|
||||
cannot access to option "Filename" because has property "mandatory"
|
||||
|
||||
A `mandatory` multi must have at least one value. This value is check only in read only mode.
|
||||
|
||||
If we remove the `mandatory` property, the value is valid:
|
||||
|
||||
>>> config.property.read_write()
|
||||
>>> config.option('new.filename').property.pop('mandatory')
|
||||
>>> config.option('new.filename').property.get()
|
||||
{'unique', 'empty'}
|
||||
>>> config.property.read_only()
|
||||
>>> config.option('new.filename').value.get()
|
||||
[]
|
||||
|
||||
A `empty` multi can has no value, but if you set a value, it must not be None:
|
||||
|
||||
>>> config.property.read_write()
|
||||
>>> config.option('new.filename').value.set(['/etc', None])
|
||||
>>> config.property.read_only()
|
||||
>>> try:
|
||||
... config.option('new.filename').value.get()
|
||||
... except PropertiesOptionError as err:
|
||||
... print(err)
|
||||
cannot access to option "Filename" because has property "empty"
|
||||
|
||||
Trying now without this property:
|
||||
|
||||
>>> config.property.read_write()
|
||||
>>> config.option('new.filename').property.pop('empty')
|
||||
>>> config.option('new.filename').value.set(['/etc', None])
|
||||
>>> config.property.read_only()
|
||||
>>> config.option('new.filename').value.get()
|
||||
['/etc', None]
|
||||
|
||||
A `unique` property in multi means you cannot have same value twice:
|
||||
|
||||
>>> config.property.read_write()
|
||||
>>> try:
|
||||
... config.option('new.filename').value.set(['/etc', '/etc'])
|
||||
... except ValueError as err:
|
||||
... print(err)
|
||||
"['/etc', '/etc']" is an invalid file name for "Filename", the value "/etc" is not unique
|
||||
|
||||
When removing this property:
|
||||
|
||||
>>> config.property.read_write()
|
||||
>>> config.option('new.filename').property.pop('unique')
|
||||
>>> config.option('new.filename').value.set(['/etc', '/etc'])
|
||||
>>> config.property.read_only()
|
||||
>>> config.option('new.filename').value.get()
|
||||
['/etc', '/etc']
|
||||
|
||||
Non-modifiable option
|
||||
=====================
|
||||
|
||||
Freeze an option means that you cannot change the value of this option:
|
||||
|
||||
>>> config = Config(root)
|
||||
>>> config.property.read_write()
|
||||
>>> config.option('new.filename').value.set(['unknown'])
|
||||
>>> config.option('new.create', 0).value.set(False)
|
||||
>>> config.option('new.create', 0).property.add('frozen')
|
||||
>>> try:
|
||||
... config.option('new.create', 0).value.set(False)
|
||||
... except PropertiesOptionError as err:
|
||||
... print(err)
|
||||
cannot modify the option "Create automaticly the file" because has property "frozen"
|
||||
|
||||
Sometime (for example when an option is calculated) we want retrieve the default value (so the calculated value) when we add `frozen` option.
|
||||
|
||||
In the current example, `new.exists` is a calculated value and we don't want that the used modify this option. So we add `frozen` and `force_default_on_freeze` properties.
|
||||
|
||||
For example, without mode, we can modify the `new.exists` option, but in `read_only` mode, we want to have default value:
|
||||
|
||||
>>> config = Config(root)
|
||||
>>> config.option('new.filename').value.set(['unknown'])
|
||||
>>> config.option('new.exists', 0).value.set(True)
|
||||
>>> config.option('new.exists', 0).value.get()
|
||||
True
|
||||
>>> config.property.read_write()
|
||||
>>> config.option('new.exists', 0).value.get()
|
||||
False
|
||||
|
||||
The property `force_default_on_freeze` is also avalaible in the option `new.type`. If the file exists, the type is calculated but if it not already exists, the user needs to set the correct wanted type.
|
31
docs/api_property.rst
Normal file
|
@ -0,0 +1,31 @@
|
|||
==================================
|
||||
Playing with property
|
||||
==================================
|
||||
|
||||
Properties and permissives affect the Tiramisu behaviour.
|
||||
|
||||
This mechanism makes available or not other options. It also controls the behavior of options.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
api_global_properties
|
||||
api_global_permissives
|
||||
api_option_property
|
||||
api_option_permissive
|
||||
|
||||
|
||||
|
||||
.. class TiramisuContextValue(TiramisuConfig):
|
||||
.. def mandatory(self):
|
||||
.. """Return path of options with mandatory property without any value"""
|
||||
..
|
||||
|
||||
.. class TiramisuOptionPermissive(CommonTiramisuOption):
|
||||
.. """Manage option's permissive"""
|
||||
.. def get(self):
|
||||
.. """Get permissives value"""
|
||||
.. def set(self, permissives):
|
||||
.. """Set permissives value"""
|
||||
.. def reset(self):
|
||||
.. """Reset all personalised permissive"""
|
|
@ -1,8 +1,12 @@
|
|||
# Manage values
|
||||
==================================
|
||||
Manage values
|
||||
==================================
|
||||
|
||||
## Values with options
|
||||
Values with options
|
||||
=========================
|
||||
|
||||
### Simple option
|
||||
Simple option
|
||||
----------------------------
|
||||
|
||||
Begin by creating a Config. This Config will contains two options:
|
||||
|
||||
|
@ -11,129 +15,92 @@ Begin by creating a Config. This Config will contains two options:
|
|||
|
||||
Let's import needed object:
|
||||
|
||||
```python
|
||||
from asyncio import run
|
||||
from shutil import disk_usage
|
||||
from os.path import isdir
|
||||
from tiramisu import FilenameOption, FloatOption, OptionDescription, Config, \
|
||||
Calculation, Params, ParamValue, ParamOption, ParamSelfOption
|
||||
```
|
||||
.. literalinclude:: src/api_value.py
|
||||
:lines: 1-4
|
||||
:linenos:
|
||||
|
||||
Create a function that verify the path exists in current system:
|
||||
|
||||
```python
|
||||
def valid_is_dir(path):
|
||||
# verify if path is a directory
|
||||
if not isdir(path):
|
||||
raise ValueError('this directory does not exist')
|
||||
```
|
||||
.. literalinclude:: src/api_value.py
|
||||
:lines: 6-9
|
||||
:linenos:
|
||||
|
||||
Use this function as a :doc:`validator` in a new option call `path`:
|
||||
|
||||
```python
|
||||
filename = FilenameOption('path', 'Path', validators=[Calculation(valid_is_dir,
|
||||
Params(ParamSelfOption()))])
|
||||
```
|
||||
.. literalinclude:: src/api_value.py
|
||||
:lines: 24-25
|
||||
:linenos:
|
||||
|
||||
Create a second function that calculate the disk usage:
|
||||
|
||||
```python
|
||||
def calc_disk_usage(path, size='bytes'):
|
||||
# do not calc if path is None
|
||||
if path is None:
|
||||
return None
|
||||
if size == 'bytes':
|
||||
div = 1
|
||||
else:
|
||||
# bytes to gigabytes
|
||||
div = 1024 * 1024 * 1024
|
||||
return disk_usage(path).free / div
|
||||
```
|
||||
.. literalinclude:: src/api_value.py
|
||||
:lines: 11-21
|
||||
:linenos:
|
||||
|
||||
Add a new option call `usage` that use this function with first argument the option `path` created before:
|
||||
|
||||
```python
|
||||
usage = FloatOption('usage', 'Disk usage', Calculation(calc_disk_usage,
|
||||
Params(ParamOption(filename))))
|
||||
```
|
||||
.. literalinclude:: src/api_value.py
|
||||
:lines: 26-27
|
||||
:linenos:
|
||||
|
||||
Finally add those options in option description and a Config:
|
||||
|
||||
```python
|
||||
disk = OptionDescription('disk', 'Verify disk usage', [filename, usage])
|
||||
root = OptionDescription('root', 'root', [disk])
|
||||
async def main():
|
||||
config = await Config(root)
|
||||
await config.property.read_write()
|
||||
return config
|
||||
.. literalinclude:: src/api_value.py
|
||||
:lines: 28-31
|
||||
:linenos:
|
||||
|
||||
config = run(main())
|
||||
```
|
||||
:download:`download the config <src/api_value.py>`
|
||||
|
||||
#### Get and set a value
|
||||
Get and set a value
|
||||
'''''''''''''''''''''''''''''
|
||||
|
||||
First of all, retrieve the values of both options:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
print(await config.option('disk.path').value.get())
|
||||
print(await config.option('disk.usage').value.get())
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
>>> config.option('disk.path').value.get()
|
||||
None
|
||||
>>> config.option('disk.usage').value.get()
|
||||
None
|
||||
```
|
||||
|
||||
Enter a value of the `path` option:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
await config.option('disk.path').value.set('/')
|
||||
print(await config.option('disk.path').value.get())
|
||||
print(await config.option('disk.usage').value.get())
|
||||
>>> config.option('disk.path').value.set('/')
|
||||
|
||||
run(main())
|
||||
```
|
||||
The value is really change:
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
>>> config.option('disk.path').value.get()
|
||||
/
|
||||
|
||||
Now, calculation retrieve a value:
|
||||
|
||||
>>> config.option('disk.usage').value.get()
|
||||
668520882176.0
|
||||
```
|
||||
|
||||
When you enter a value it is validated:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
try:
|
||||
await config.option('disk.path').value.set('/unknown')
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
>>> try:
|
||||
>>> config.option('disk.path').value.set('/unknown')
|
||||
>>> except ValueError as err:
|
||||
>>> print(err)
|
||||
"/unknown" is an invalid file name for "Path", this directory does not exist
|
||||
```
|
||||
|
||||
#### Is value is valid?
|
||||
We can also set a :doc:`calculation` as value. For example, we want to launch previous function but with in_gb to True as second argument:
|
||||
|
||||
>>> calc = Calculation(calc_disk_usage, Params((ParamOption(filename),
|
||||
... ParamValue('gigabytes'))))
|
||||
>>> config.option('disk.usage').value.set(calc)
|
||||
>>> config.option('disk.usage').value.get()
|
||||
622.6080360412598
|
||||
|
||||
Is value is valid?
|
||||
'''''''''''''''''''''''''''''
|
||||
|
||||
To check is a value is valid:
|
||||
|
||||
```python
|
||||
await config.option('disk.path').value.valid()
|
||||
```
|
||||
>>> config.option('disk.path').value.valid()
|
||||
True
|
||||
|
||||
#### Display the default value
|
||||
Display the default value
|
||||
'''''''''''''''''''''''''''''
|
||||
|
||||
Even if the value is modify, you can display the default value with `default` method:
|
||||
|
||||
|
@ -144,7 +111,8 @@ Even if the value is modify, you can display the default value with `default` me
|
|||
>>> config.option('disk.usage').value.default()
|
||||
668510105600.0
|
||||
|
||||
#### Return to the default value
|
||||
Return to the default value
|
||||
'''''''''''''''''''''''''''''
|
||||
|
||||
If the value is modified, just `reset` it to retrieve the default value:
|
||||
|
||||
|
@ -155,7 +123,8 @@ If the value is modified, just `reset` it to retrieve the default value:
|
|||
>>> config.option('disk.path').value.get()
|
||||
None
|
||||
|
||||
#### The ownership of a value
|
||||
The ownership of a value
|
||||
'''''''''''''''''''''''''''''
|
||||
|
||||
Every option has an owner, that will indicate who changed the option's value last.
|
||||
|
||||
|
@ -206,26 +175,28 @@ We can change this owner:
|
|||
>>> config.option('disk.path').owner.get()
|
||||
itsme
|
||||
|
||||
### Get choices from a Choice option
|
||||
Get choices from a Choice option
|
||||
--------------------------------------
|
||||
|
||||
In the previous example, it's difficult to change the second argument of the `calc_disk_usage`.
|
||||
|
||||
For ease the change, add a `ChoiceOption` and replace the `size_type` and `disk` option:
|
||||
|
||||
.. literalinclude:: ../src/api_value_choice.py
|
||||
.. literalinclude:: src/api_value_choice.py
|
||||
:lines: 26-31
|
||||
:linenos:
|
||||
|
||||
We set the default value to `bytes`, if not, the default value will be None.
|
||||
|
||||
:download:`download the config <../src/api_value_choice.py>`
|
||||
:download:`download the config <src/api_value_choice.py>`
|
||||
|
||||
At any time, we can get all de choices avalaible for an option:
|
||||
|
||||
>>> config.option('disk.size_type').value.list()
|
||||
('bytes', 'giga bytes')
|
||||
|
||||
### Value in multi option
|
||||
Value in multi option
|
||||
--------------------------------------
|
||||
|
||||
.. FIXME undefined
|
||||
|
||||
|
@ -237,25 +208,26 @@ First of all, we have to modification in this option:
|
|||
- add multi attribute to True
|
||||
- the function use in validation valid a single value, so each value in the list must be validate separatly, for that we add whole attribute to False in `ParamSelfOption` object
|
||||
|
||||
.. literalinclude:: ../src/api_value_multi.py
|
||||
.. literalinclude:: src/api_value_multi.py
|
||||
:lines: 23-25
|
||||
:linenos:
|
||||
|
||||
Secondly, the function calc_disk_usage must return a list:
|
||||
|
||||
.. literalinclude:: ../src/api_value_multi.py
|
||||
.. literalinclude:: src/api_value_multi.py
|
||||
:lines: 11-26
|
||||
:linenos:
|
||||
|
||||
Finally `usage` option is also a multi:
|
||||
|
||||
.. literalinclude:: ../src/api_value_multi.py
|
||||
.. literalinclude:: src/api_value_multi.py
|
||||
:lines: 27-30
|
||||
:linenos:
|
||||
|
||||
:download:`download the config <../src/api_value_multi.py>`
|
||||
:download:`download the config <src/api_value_multi.py>`
|
||||
|
||||
#### Get or set a multi value
|
||||
Get or set a multi value
|
||||
'''''''''''''''''''''''''''''
|
||||
|
||||
Since the options are multi, the default value is a list:
|
||||
|
||||
|
@ -272,7 +244,8 @@ A multi option waiting for a list:
|
|||
>>> config.option('disk.usage').value.get()
|
||||
[668499898368.0, 8279277568.0]
|
||||
|
||||
#### The ownership of multi option
|
||||
The ownership of multi option
|
||||
'''''''''''''''''''''''''''''
|
||||
|
||||
There is no difference in behavior between a simple option and a multi option:
|
||||
|
||||
|
@ -285,7 +258,8 @@ default
|
|||
>>> config.option('disk.path').owner.get()
|
||||
user
|
||||
|
||||
### Leadership
|
||||
Leadership
|
||||
--------------------------------------
|
||||
|
||||
In previous example, we cannot define different `size_type` for each path. If you want do this, you need a leadership.
|
||||
|
||||
|
@ -295,23 +269,24 @@ As each value of followers are isolate, the function `calc_disk_usage` will rece
|
|||
|
||||
So let's change this function:
|
||||
|
||||
.. literalinclude:: ../src/api_value_leader.py
|
||||
.. literalinclude:: src/api_value_leader.py
|
||||
:lines: 12-18
|
||||
:linenos:
|
||||
|
||||
Secondly the option `size_type` became a multi:
|
||||
|
||||
.. literalinclude:: ../src/api_value_leader.py
|
||||
.. literalinclude:: src/api_value_leader.py
|
||||
:lines: 24-25
|
||||
:linenos:
|
||||
|
||||
Finally disk has to be a leadership:
|
||||
|
||||
.. literalinclude:: ../src/api_value_leader.py
|
||||
.. literalinclude:: src/api_value_leader.py
|
||||
:lines: 30
|
||||
:linenos:
|
||||
|
||||
#### Get and set a leader
|
||||
Get and set a leader
|
||||
'''''''''''''''''''''''''''''
|
||||
|
||||
A leader is, in fact, a multi option:
|
||||
|
||||
|
@ -346,7 +321,8 @@ To reduce use the `pop` method:
|
|||
>>> config.option('disk.path').value.get()
|
||||
['/']
|
||||
|
||||
#### Get and set a follower
|
||||
Get and set a follower
|
||||
'''''''''''''''''''''''''''''
|
||||
|
||||
As followers are isolate, we cannot get all the follower values:
|
||||
|
||||
|
@ -376,7 +352,8 @@ As the leader, follower has a length (in fact, this is the leader's length):
|
|||
>>> config.option('disk.size_type').value.len()
|
||||
2
|
||||
|
||||
#### The ownership of a leader and follower
|
||||
The ownership of a leader and follower
|
||||
'''''''''''''''''''''''''''''''''''''''''''
|
||||
|
||||
There is no differences between a multi option and a leader option:
|
||||
|
||||
|
@ -396,37 +373,36 @@ True
|
|||
>>> config.option('disk.size_type', 1).owner.get()
|
||||
default
|
||||
|
||||
## Values in option description
|
||||
Values in option description
|
||||
==============================
|
||||
|
||||
With an option description we can have directly a dict with all option's name and value:
|
||||
|
||||
>>> config.option('disk.path').value.set(['/', '/tmp'])
|
||||
>>> config.option('disk.size_type', 0).value.set('giga bytes')
|
||||
>>> config.option('disk').value.dict()
|
||||
{'path': ['/', '/tmp'], 'size_type': ['giga bytes', 'bytes'], 'usage': [622.578239440918, 8279273472.0]}
|
||||
|
||||
An attribute fullpath permit to have fullpath of child option:
|
||||
|
||||
>>> config.option('disk').value.dict(fullpath=True)
|
||||
>>> config.option('disk').value.get()
|
||||
{'disk.path': ['/', '/tmp'], 'disk.size_type': ['giga bytes', 'bytes'], 'disk.usage': [622.578239440918, 8279273472.0]}
|
||||
|
||||
## Values in config
|
||||
Values in config
|
||||
==========================
|
||||
|
||||
###dict
|
||||
get
|
||||
--------
|
||||
|
||||
With the `config` we can have directly a dict with all option's name and value:
|
||||
|
||||
>>> config.option('disk.path').value.set(['/', '/tmp'])
|
||||
>>> config.option('disk.size_type', 0).value.set('giga bytes')
|
||||
>>> config.value.dict()
|
||||
>>> config.value.get()
|
||||
{'disk.path': ['/', '/tmp'], 'disk.size_type': ['giga bytes', 'bytes'], 'disk.usage': [622.578239440918, 8279273472.0]}
|
||||
|
||||
If you don't wan't path but only the name:
|
||||
|
||||
>>> config.value.dict(flatten=True)
|
||||
>>> config.value.get(flatten=True)
|
||||
{'path': ['/', '/tmp'], 'size_type': ['giga bytes', 'bytes'], 'usage': [622.578239440918, 8279273472.0]}
|
||||
|
||||
### importation/exportation
|
||||
importation/exportation
|
||||
------------------------
|
||||
|
||||
In config, we can export full values:
|
||||
|
||||
|
@ -439,4 +415,3 @@ and reimport it later:
|
|||
>>> config.value.importation(export)
|
||||
|
||||
.. note:: The exportation format is not stable and can be change later, please do not use importation otherwise than jointly with exportation.
|
||||
|
119
docs/application.rst
Normal file
|
@ -0,0 +1,119 @@
|
|||
==================================
|
||||
A full application
|
||||
==================================
|
||||
|
||||
The firefox network configuration
|
||||
-------------------------------------
|
||||
|
||||
Now we are going to resume everything we have seen with a concrete example.
|
||||
We're going to take an example based on the `Mozilla Firefox
|
||||
<https://www.mozilla.org/en-US/firefox/>`_ proxy's
|
||||
configuration, like what is required when you open the `network settings` in
|
||||
the General configuration's firefox page:
|
||||
|
||||
.. image:: images/firefox_preferences.png
|
||||
|
||||
The tiramisu's configuration
|
||||
----------------------------
|
||||
|
||||
Build the `Config`
|
||||
''''''''''''''''''''
|
||||
|
||||
First, let's create our options :
|
||||
|
||||
.. literalinclude:: src/application.py
|
||||
:lines: 1-3, 12-20
|
||||
:linenos:
|
||||
|
||||
This first option is the most important one : its value will determine which other options
|
||||
are disabled and which are not. The same thing will happen with other options later.
|
||||
Here are the others options we'll be using :
|
||||
|
||||
.. literalinclude:: src/application.py
|
||||
:lines: 23-221
|
||||
:linenos:
|
||||
|
||||
As you can see, we're using :doc:`value <api_value>`, :doc:`property <api_option_property>`
|
||||
and :doc:`calculation` in the setting of our options, because we have many options which
|
||||
value or accessibility is depending on the value of other options.
|
||||
|
||||
Now we need to create OptionDescriptions and configs :
|
||||
|
||||
.. literalinclude:: src/application.py
|
||||
:lines: 223-232
|
||||
:linenos:
|
||||
|
||||
Download the :download:`full code <src/application.py>` of this example.
|
||||
|
||||
As you can see, we regrouped a lot of options in 'protocols', so we can set a calculated `disabled` property
|
||||
that is apply to all those options. This way, we don't have to put the instruction on every option
|
||||
one by one.
|
||||
|
||||
Let's try
|
||||
'''''''''''''''
|
||||
|
||||
Now that we have our Config, it's time to run some tests !
|
||||
Here are a few code blocks you can test and the results you should get :
|
||||
|
||||
1. Automatic proxy configuration URL:
|
||||
|
||||
>>> proxy_config.property.read_write()
|
||||
>>> proxy_config.option('proxy_mode').value.set('Automatic proxy configuration URL')
|
||||
>>> proxy_config.option('auto_config_url').value.set('http://192.168.1.1/wpad.dat')
|
||||
>>> proxy_config.property.read_only()
|
||||
>>> for path, value in proxy_config.value.get().items():
|
||||
... print(proxy_config.option(path).option.doc() + ': "' + str(value) + '"')
|
||||
Proxy's config mode: "Automatic proxy configuration URL"
|
||||
Address for which proxy will be desactivated: "[]"
|
||||
Proxy's auto config URL: "http://192.168.1.1/wpad.dat"
|
||||
Prompt for authentication if password is saved: "False"
|
||||
Enable DNS over HTTPS: "False"
|
||||
|
||||
2. Auto-detect proxy settings for this network:
|
||||
|
||||
>>> proxy_config.property.read_write()
|
||||
>>> proxy_config.option('proxy_mode').value.set('Auto-detect proxy settings for this network')
|
||||
>>> proxy_config.option('no_proxy').value.set(['localhost',
|
||||
... '127.0.0.1',
|
||||
... '192.16.10.150',
|
||||
... '192.168.5.101',
|
||||
... '192.168.56.101/32',
|
||||
... '192.168.20.0/24',
|
||||
... '.tiramisu.org',
|
||||
... 'mozilla.org'])
|
||||
>>> proxy_config.option('dns_over_https.enable_dns_over_https').value.set(True)
|
||||
>>> proxy_config.option('dns_over_https.used_dns').value.set('default')
|
||||
>>> proxy_config.property.read_only()
|
||||
>>> for path, value in proxy_config.value.get().items():
|
||||
... print(proxy_config.option(path).option.doc() + ': "' + str(value) + '"')
|
||||
Proxy's config mode: "Auto-detect proxy settings for this network"
|
||||
Address for which proxy will be desactivated: "['localhost', '127.0.0.1', '192.16.10.150', '192.168.5.101', '192.168.56.101/32', '192.168.20.0/24', '.tiramisu.org', 'mozilla.org']"
|
||||
Prompt for authentication if password is saved: "False"
|
||||
Enable DNS over HTTPS: "True"
|
||||
Used DNS: "default"
|
||||
|
||||
Set use_for_all_protocols to True:
|
||||
|
||||
>>> proxy_config.property.read_write()
|
||||
>>> proxy_config.option('protocols.use_for_all_protocols').value.set(True)
|
||||
>>> proxy_config.property.read_only()
|
||||
>>> for path, value in proxy_config.value.get().items():
|
||||
... print(proxy_config.option(path).option.doc() + ': "' + str(value) + '"')
|
||||
Proxy's config mode: "Manual proxy configuration"
|
||||
Address: "192.168.20.1"
|
||||
Port: "8080"
|
||||
Use HTTP IP and Port for all protocols: "True"
|
||||
Address: "192.168.20.1"
|
||||
Port: "8080"
|
||||
Address: "192.168.20.1"
|
||||
Port: "8080"
|
||||
Address: "192.168.20.1"
|
||||
Port: "8080"
|
||||
SOCKS host version used by proxy: "v5"
|
||||
Address for which proxy will be desactivated: "[]"
|
||||
Prompt for authentication if password is saved: "False"
|
||||
Use Proxy DNS when using SOCKS v5: "False"
|
||||
Enable DNS over HTTPS: "True"
|
||||
Used DNS: "custom"
|
||||
Custom DNS URL: "https://dns-url.com"
|
||||
|
159
docs/browse.rst
Normal file
|
@ -0,0 +1,159 @@
|
|||
Browse the :class:`Config`
|
||||
===========================
|
||||
|
||||
Getting the options
|
||||
----------------------
|
||||
|
||||
.. note:: The :class:`Config` object we are using is located here in this script:
|
||||
|
||||
:download:`download the source <src/property.py>`
|
||||
|
||||
Let's retrieve the config object, named `cfg`
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from property import cfg
|
||||
|
||||
We retrieve by path an option named `var1`
|
||||
and then we retrieve its name and its docstring
|
||||
|
||||
.. code-block:: bash
|
||||
:emphasize-lines: 2, 5, 8
|
||||
|
||||
print(cfg.option('od1.var1'))
|
||||
<tiramisu.api.TiramisuOption object at 0x7f3876cc5940>
|
||||
|
||||
print(cfg.option('od1.var1').option.name())
|
||||
'var1'
|
||||
|
||||
print(cfg.option('od1.var1').option.doc())
|
||||
'first option'
|
||||
|
||||
|
||||
Accessing the values of the options
|
||||
-------------------------------------
|
||||
|
||||
Let's browse the configuration structure and option values.
|
||||
|
||||
You have getters as a `get` method on option objects:
|
||||
|
||||
|
||||
.. code-block:: bash
|
||||
:emphasize-lines: 10, 14
|
||||
|
||||
# getting all the options
|
||||
print(cfg.option.value.get())
|
||||
{'var1': None, 'var2': 'value'}
|
||||
|
||||
# getting the `od1` option description
|
||||
print(cfg.option('od1').value.get())
|
||||
{'od1.var1': None, 'od1.var2': 'value'}
|
||||
|
||||
# getting the var1 option's value
|
||||
print(cfg.option('od1.var1').value.get())
|
||||
None
|
||||
|
||||
# getting the var2 option's default value
|
||||
print(cfg.option('od1.var2').value.get())
|
||||
'value'
|
||||
|
||||
# trying to get a non existent option's value
|
||||
cfg.option('od1.idontexist').value.get()
|
||||
AttributeError: unknown option "idontexist" in optiondescription "od1"
|
||||
|
||||
|
||||
Setting the value of an option
|
||||
------------------------------
|
||||
|
||||
An important part of the setting's configuration consists of setting the
|
||||
value's option.
|
||||
|
||||
|
||||
You have setters as a `set` method on option objects.
|
||||
|
||||
And if you wanna come back to a default value, use the `reset()` method.
|
||||
|
||||
.. code-block:: bash
|
||||
:emphasize-lines: 2
|
||||
|
||||
# changing the `od1.var1` value
|
||||
cfg.option('od1.var1').value.set('éééé')
|
||||
print(cfg.option('od1.var1').value.get())
|
||||
'éééé'
|
||||
|
||||
# carefull to the type of the value to be set
|
||||
cfg.option('od1.var1').value.set(23454)
|
||||
ValueError: "23454" is an invalid string for "first variable"
|
||||
|
||||
# let's come back to the default value
|
||||
cfg.option('od1.var2').value.reset()
|
||||
print(cfg.option('od1.var2').value.get())
|
||||
'value'
|
||||
|
||||
.. important:: If the config is `read only`, setting an option's value isn't allowed, see :doc:`property`
|
||||
|
||||
|
||||
Let's make the protocol of accessing a `Config`'s option explicit
|
||||
(because explicit is better than implicit):
|
||||
|
||||
1. If the option has not been declared, an `Error` is raised,
|
||||
2. If an option is declared, but neither a value nor a default value has
|
||||
been set, the returned value is `None`,
|
||||
3. If an option is declared and a default value has been set, but no value
|
||||
has been set, the returned value is the default value of the option,
|
||||
|
||||
4. If an option is declared, and a value has been set, the returned value is
|
||||
the value of the option.
|
||||
|
||||
But there are special exceptions. We will see later on that an option can be a
|
||||
:term:`mandatory option`. A mandatory option is an option that must have a value
|
||||
defined.
|
||||
|
||||
Searching for an option
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In an application, knowing the path of an option is not always feasible.
|
||||
That's why a tree of options can easily be searched with the :func:`find()` method.
|
||||
|
||||
Let's find an option by it's name
|
||||
|
||||
And let's find first an option by it's name
|
||||
|
||||
The search can be performed in a subtree
|
||||
|
||||
.. code-block:: bash
|
||||
:emphasize-lines: 1, 6, 19
|
||||
|
||||
print(cfg.option.find(name='var1'))
|
||||
# [<tiramisu.api.TiramisuOption object at 0x7f490a530f98>, <tiramisu.api.TiramisuOption object at 0x7f490a530748>]
|
||||
|
||||
# If the option name is unique, the search can be stopped once one matched option
|
||||
# has been found:
|
||||
print(cfg.option.find(name='var1', first=True))
|
||||
# <tiramisu.api.TiramisuOption object at 0x7f6c2beae128>
|
||||
|
||||
# a search object behaves like a cfg object, for example
|
||||
print(cfg.option.find(name='var1', first=True).option.name())
|
||||
# 'var1'
|
||||
print(cfg.option.find(name='var1', first=True).option.doc())
|
||||
|
||||
# a search can be made with various criteria
|
||||
print(cfg.option.find(name='var3', value=undefined))
|
||||
print(cfg.option.find(name='var3', type=StrOption))
|
||||
|
||||
# the find method can be used in subconfigs
|
||||
print(cfg.option('od2').find('var1'))
|
||||
|
||||
:download:`download the config used for the find <src/find.py>`
|
||||
|
||||
The `get` flattening utility
|
||||
-------------------------------------
|
||||
|
||||
In a config or a subconfig, you can print a dict-like representation
|
||||
|
||||
.. code-block:: bash
|
||||
:emphasize-lines: 2
|
||||
|
||||
# get the `od1` option description
|
||||
print(cfg.option('od1').value.get())
|
||||
{'od1.var1': 'éééé', 'od1.var2': 'value'}
|
129
docs/calculation.rst
Normal file
|
@ -0,0 +1,129 @@
|
|||
==================================
|
||||
Calculation
|
||||
==================================
|
||||
|
||||
Calculation is a generic object that allow you to call an external function.
|
||||
|
||||
Simple calculation
|
||||
==================================
|
||||
|
||||
It's structure is the following :
|
||||
|
||||
.. literalinclude:: src/calculation.py
|
||||
:lines: 1-5
|
||||
:linenos:
|
||||
|
||||
Positional and keyword arguments
|
||||
=========================================
|
||||
|
||||
Function's arguments are also specified in this object.
|
||||
Let's see with a positional argument and a keyword argument:
|
||||
|
||||
.. literalinclude:: src/calculation.py
|
||||
:lines: 8-10
|
||||
:linenos:
|
||||
|
||||
The function `a_function_with_parameters` will be call with the positional argument `value1` to `my value 1` and the keyword argument `value2` to `my value 2`.
|
||||
So when this function will be executed, it will return `my value 1 my value 2`.
|
||||
|
||||
Let's see with two positional arguments:
|
||||
|
||||
.. literalinclude:: src/calculation.py
|
||||
:lines: 13-15
|
||||
:linenos:
|
||||
|
||||
As we have several positional arguments, the first Params' argument is a tuple.
|
||||
This example will return strictly same result has previous example.
|
||||
|
||||
Option has an argument
|
||||
=========================================
|
||||
|
||||
In previous examples, we use ParamValue arguments, which could contain random value. But this value is static and cannot be change.
|
||||
|
||||
It could be interesting to use an existant option has an argument:
|
||||
|
||||
.. literalinclude:: src/calculation.py
|
||||
:lines: 18-21
|
||||
:linenos:
|
||||
|
||||
As long as option1 is at its default value, the function will return `1`. If we set option1 to `12`, the function will return `12`.
|
||||
|
||||
Pay attention to the properties when you use an option.
|
||||
This example will raise a ConfigError:
|
||||
|
||||
.. literalinclude:: src/calculation.py
|
||||
:lines: 24-27
|
||||
:linenos:
|
||||
|
||||
It's up to you to define the desired behavior.
|
||||
|
||||
If you want the option to be transitively disabled just set the raisepropertyerror argument to True:
|
||||
|
||||
.. literalinclude:: src/calculation.py
|
||||
:lines: 29-31
|
||||
:linenos:
|
||||
|
||||
If you want to remove option in argument, just set the notraisepropertyerror argument to True:
|
||||
|
||||
.. literalinclude:: src/calculation.py
|
||||
:lines: 33-35
|
||||
:linenos:
|
||||
|
||||
In this case, option1 will not pass to function. You have to set a default value to this argument.
|
||||
So, function will return `None`.
|
||||
|
||||
In these examples, the function only accesses to the value of the option. But no additional information is given.
|
||||
It is possible to add the parameter `todict` to `True` to have the description of the option in addition to its value.
|
||||
|
||||
.. literalinclude:: src/calculation.py
|
||||
:lines: 37-39
|
||||
:linenos:
|
||||
|
||||
This function will return `the option first option has value 1`.
|
||||
|
||||
Multi option has an argument
|
||||
=========================================
|
||||
|
||||
An option could be a multi. Here is an example:
|
||||
|
||||
.. literalinclude:: src/calculation.py
|
||||
:lines: 46-49
|
||||
:linenos:
|
||||
|
||||
In this case the function will return the complete list. So `[1]` in this example.
|
||||
|
||||
Leader or follower option has an argument
|
||||
============================================
|
||||
|
||||
An option could be a leader:
|
||||
|
||||
.. literalinclude:: src/calculation.py
|
||||
:lines: 51-57
|
||||
:linenos:
|
||||
|
||||
If the calculation is used in a standard multi, it will return `[1]`.
|
||||
If the calculation is used in a follower, it will return `1`.
|
||||
|
||||
An option could be a follower:
|
||||
|
||||
.. literalinclude:: src/calculation.py
|
||||
:lines: 59-65
|
||||
:linenos:
|
||||
|
||||
If the calculation is used in a standard multi, it will return `[2]`.
|
||||
If the calculation is used in a follower, it will return `2`.
|
||||
|
||||
If the calculation is used in a follower we can also retrieve the actual follower index:
|
||||
|
||||
.. literalinclude:: src/calculation.py
|
||||
:lines: 67-73
|
||||
:linenos:
|
||||
|
||||
Context has an argument
|
||||
=========================================
|
||||
|
||||
It is possible to recover a copy of the context directly in a function. On the other hand, the use of the context in a function is a slow action which will haunt the performances. Use it only in case of necessity:
|
||||
|
||||
.. literalinclude:: src/calculation.py
|
||||
:lines: 42-44
|
||||
:linenos:
|
718
docs/cmdline_parser.rst
Normal file
|
@ -0,0 +1,718 @@
|
|||
.. .. default-role:: code
|
||||
..
|
||||
.. ==========================
|
||||
.. Tiramisu-cmdline-parser
|
||||
.. ==========================
|
||||
..
|
||||
..
|
||||
.. This tutorial is intended to be a gentle introduction to **Tiramisu
|
||||
.. command-line parser**, a command-line parsing module that comes included with
|
||||
.. the **Tiramisu**'s library.
|
||||
..
|
||||
.. .. note:: There are a lot of other modules that fulfill the same task,
|
||||
.. namely getopt (an equivalent for getopt() from the C language) and
|
||||
.. argparse, from the python standard library.
|
||||
..
|
||||
.. `tiramisu-cmdline-parser` enables us to *validate* the command line,
|
||||
.. wich is a quite different scope -- much more powerfull. It is a
|
||||
.. superset of the argparse_ module
|
||||
..
|
||||
.. .. _argparse: https://docs.python.org/3/howto/argparse.html
|
||||
..
|
||||
.. What is Tiramisu-cmdline-parser ?
|
||||
.. ==================================
|
||||
..
|
||||
.. Tiramisu-cmdline-parser is a free project that turns Tiramisu's Config into a command line interface.
|
||||
..
|
||||
.. It automatically generates arguments, help and usage messages. Tiramisu (or
|
||||
.. Tiramisu-API) validates all arguments provided by the command line's user.
|
||||
..
|
||||
.. Tiramisu-cmdline-parser uses the well known argparse_ module and adds
|
||||
.. functionnalities upon it.
|
||||
..
|
||||
..
|
||||
.. Installation
|
||||
.. ==============
|
||||
..
|
||||
.. The best way is to use the python pip_ installer
|
||||
..
|
||||
.. .. _pip: https://pip.pypa.io/en/stable/installing/
|
||||
..
|
||||
.. And then type:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. pip install tiramisu-cmdline-parser
|
||||
..
|
||||
.. Build a Tiramisu-cmdline-parser
|
||||
.. =================================
|
||||
..
|
||||
.. Let’s show the sort of functionality that we are going to explore in this
|
||||
.. introductory tutorial.
|
||||
..
|
||||
.. We are going to start with a simple example, like making a proxy's
|
||||
.. configuration script.
|
||||
..
|
||||
.. First we are going to build the corresponding `Tiramisu` config object:
|
||||
..
|
||||
.. .. literalinclude:: src/proxy.py
|
||||
.. :lines: 1-44
|
||||
.. :linenos:
|
||||
.. :name: Proxy1
|
||||
..
|
||||
.. Then we invopque the command line parsing library by creating a commandline
|
||||
.. parser, and we give the configuration's object to it:
|
||||
..
|
||||
.. .. literalinclude:: src/proxy.py
|
||||
.. :lines: 46-48
|
||||
.. :linenos:
|
||||
.. :name: Proxy2
|
||||
..
|
||||
.. Finally pretty printing the configuration:
|
||||
..
|
||||
.. .. literalinclude:: src/proxy.py
|
||||
.. :lines: 50-51
|
||||
.. :linenos:
|
||||
.. :name: Proxy3
|
||||
..
|
||||
.. Let's display the help:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py -h
|
||||
.. usage: proxy.py [-h] [--dns_over_https] [--no-dns_over_https]
|
||||
.. {No proxy,Manual proxy configuration,Automatic proxy
|
||||
.. configuration URL}
|
||||
..
|
||||
.. positional arguments:
|
||||
.. {No proxy,Manual proxy configuration,Automatic proxy configuration URL}
|
||||
.. Proxy's config mode
|
||||
..
|
||||
.. optional arguments:
|
||||
.. -h, --help show this help message and exit
|
||||
.. --dns_over_https Enable DNS over HTTPS
|
||||
.. --no-dns_over_https
|
||||
..
|
||||
.. Positional argument
|
||||
.. ======================
|
||||
..
|
||||
.. First of all, we have to set the positional argument :option:`proxy_mode`.
|
||||
..
|
||||
.. .. option:: proxy_mode
|
||||
..
|
||||
.. As it's a `ChoiceOption`, you only have three choices:
|
||||
..
|
||||
.. - No proxy
|
||||
.. - Manual proxy configuration
|
||||
.. - Automatic proxy configuration URL
|
||||
..
|
||||
.. Set proxy_mode to `No proxy`:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "No proxy"
|
||||
.. {'dns_over_https': False,
|
||||
.. 'proxy_mode': 'No proxy'}
|
||||
..
|
||||
.. Requirements
|
||||
.. ================
|
||||
..
|
||||
.. Disabled options are not visible as arguments in the command line.
|
||||
.. Those parameters appears or disappears following the context:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "No proxy" -h
|
||||
.. usage: proxy.py "No proxy" [-h] [--dns_over_https] [--no-dns_over_https]
|
||||
.. {No proxy,Manual proxy configuration,Automatic
|
||||
.. proxy configuration URL}
|
||||
..
|
||||
.. positional arguments:
|
||||
.. {No proxy,Manual proxy configuration,Automatic proxy configuration URL}
|
||||
.. Proxy's config mode
|
||||
..
|
||||
.. optional arguments:
|
||||
.. -h, --help show this help message and exit
|
||||
.. --dns_over_https Enable DNS over HTTPS
|
||||
.. --no-dns_over_https
|
||||
..
|
||||
.. If proxy_mode is set to "Automatic proxy configuration URL", some new options are visible:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "Automatic proxy configuration URL" -h
|
||||
.. usage: proxy.py "Automatic proxy configuration URL" [-h] -i AUTO_CONFIG_URL
|
||||
.. [--no_proxy.no_proxy_network.no_proxy_network [NO_PROXY_NETWORK [NO_PROXY_NETWORK ...]]]
|
||||
.. [--no_proxy.no_proxy_network.pop-no_proxy_network INDEX]
|
||||
.. --no_proxy.no_proxy_network.no_proxy_netmask
|
||||
.. INDEX NO_PROXY_NETMASK
|
||||
.. [--no_proxy.no_proxy_domain [NO_PROXY_DOMAIN [NO_PROXY_DOMAIN ...]]]
|
||||
.. [--dns_over_https]
|
||||
.. [--no-dns_over_https]
|
||||
.. {No proxy,Manual proxy
|
||||
.. configuration,Automatic
|
||||
.. proxy configuration URL}
|
||||
..
|
||||
.. positional arguments:
|
||||
.. {No proxy,Manual proxy configuration,Automatic proxy configuration URL}
|
||||
.. Proxy's config mode
|
||||
..
|
||||
.. optional arguments:
|
||||
.. -h, --help show this help message and exit
|
||||
.. --dns_over_https Enable DNS over HTTPS
|
||||
.. --no-dns_over_https
|
||||
..
|
||||
.. configuration.automatic_proxy:
|
||||
.. Automatic proxy setting
|
||||
..
|
||||
.. -i AUTO_CONFIG_URL, --configuration.automatic_proxy.auto_config_url AUTO_CONFIG_URL
|
||||
.. Proxy's auto config URL
|
||||
..
|
||||
.. no_proxy:
|
||||
.. Disabled proxy
|
||||
..
|
||||
.. --no_proxy.no_proxy_domain [NO_PROXY_DOMAIN [NO_PROXY_DOMAIN ...]]
|
||||
.. Domain names for which proxy will be desactivated
|
||||
..
|
||||
.. no_proxy.no_proxy_network:
|
||||
.. Network for which proxy will be desactivated
|
||||
..
|
||||
.. --no_proxy.no_proxy_network.no_proxy_network [NO_PROXY_NETWORK [NO_PROXY_NETWORK ...]]
|
||||
.. Network addresses
|
||||
.. --no_proxy.no_proxy_network.pop-no_proxy_network INDEX
|
||||
.. --no_proxy.no_proxy_network.no_proxy_netmask INDEX NO_PROXY_NETMASK
|
||||
.. Netmask addresses
|
||||
..
|
||||
.. Arguments
|
||||
.. ===========
|
||||
..
|
||||
.. Each option creates an argument. To change the value of this option, just launch the application with the appropriate argument:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "Manual proxy configuration" \
|
||||
.. --configuration.manual_proxy.http_ip_address 192.168.1.1
|
||||
.. {'configuration.manual_proxy.http_ip_address': '192.168.1.1',
|
||||
.. 'configuration.manual_proxy.http_port': '8080',
|
||||
.. 'configuration.manual_proxy.i': '192.168.1.1',
|
||||
.. 'configuration.manual_proxy.p': '8080',
|
||||
.. 'dns_over_https': False,
|
||||
.. 'no_proxy.no_proxy_domain': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_netmask': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_network': [],
|
||||
.. 'proxy_mode': 'Manual proxy configuration'}
|
||||
..
|
||||
.. Fullpath argument or named argument
|
||||
.. =====================================
|
||||
..
|
||||
.. By default, arguments are build with fullpath of option.
|
||||
.. The `option http_ip_address` is in `manual_proxy` optiondescription, which is also in configuration optiondescription.
|
||||
.. So the argument is :option:`--configuration.manual_proxy.http_ip_address`:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "Manual proxy configuration" \
|
||||
.. --configuration.manual_proxy.http_ip_address 192.168.1.1
|
||||
.. {'configuration.manual_proxy.http_ip_address': '192.168.1.1',
|
||||
.. 'configuration.manual_proxy.http_port': '8080',
|
||||
.. 'configuration.manual_proxy.i': '192.168.1.1',
|
||||
.. 'configuration.manual_proxy.p': '8080',
|
||||
.. 'dns_over_https': False,
|
||||
.. 'no_proxy.no_proxy_domain': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_netmask': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_network': [],
|
||||
.. 'proxy_mode': 'Manual proxy configuration'}
|
||||
..
|
||||
.. If we set fullpath to `False`:
|
||||
..
|
||||
.. .. code-block:: python
|
||||
..
|
||||
.. parser = TiramisuCmdlineParser(proxy_config, fullpath=False)
|
||||
..
|
||||
.. Arguments are build with the name of the option.
|
||||
.. The option :option:`http_ip_address` is now :option`--http_ip_address`:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "Manual proxy configuration" \
|
||||
.. --http_ip_address 192.168.1.1
|
||||
.. {'configuration.manual_proxy.http_ip_address': '192.168.1.1',
|
||||
.. 'configuration.manual_proxy.http_port': '8080',
|
||||
.. 'configuration.manual_proxy.i': '192.168.1.1',
|
||||
.. 'configuration.manual_proxy.p': '8080',
|
||||
.. 'dns_over_https': False,
|
||||
.. 'no_proxy.no_proxy_domain': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_netmask': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_network': [],
|
||||
.. 'proxy_mode': 'Manual proxy configuration'}
|
||||
..
|
||||
.. Short argument
|
||||
.. ===============
|
||||
..
|
||||
.. To have short argument, you just have to make `SymLinkOption` to this option:
|
||||
..
|
||||
.. .. literalinclude:: src/proxy.py
|
||||
.. :lines: 10-11
|
||||
.. :linenos:
|
||||
.. :name: Proxy4
|
||||
..
|
||||
.. Now argument `-i` or `--configuration.manual_proxy.http_ip_address` can be used alternatively:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "Manual proxy configuration" \
|
||||
.. --configuration.manual_proxy.http_ip_address 192.168.1.1
|
||||
.. {'configuration.manual_proxy.http_ip_address': '192.168.1.1',
|
||||
.. 'configuration.manual_proxy.http_port': '8080',
|
||||
.. 'configuration.manual_proxy.i': '192.168.1.1',
|
||||
.. 'configuration.manual_proxy.p': '8080',
|
||||
.. 'dns_over_https': False,
|
||||
.. 'no_proxy.no_proxy_domain': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_netmask': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_network': [],
|
||||
.. 'proxy_mode': 'Manual proxy configuration'}
|
||||
..
|
||||
..
|
||||
.. $ python3 src/proxy.py "Manual proxy configuration" \
|
||||
.. -i 192.168.1.1
|
||||
.. {'configuration.manual_proxy.http_ip_address': '192.168.1.1',
|
||||
.. 'configuration.manual_proxy.http_port': '8080',
|
||||
.. 'configuration.manual_proxy.i': '192.168.1.1',
|
||||
.. 'configuration.manual_proxy.p': '8080',
|
||||
.. 'dns_over_https': False,
|
||||
.. 'no_proxy.no_proxy_domain': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_netmask': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_network': [],
|
||||
.. 'proxy_mode': 'Manual proxy configuration'}
|
||||
..
|
||||
.. Be carefull, short argument have to be uniqe in the whole configuration.
|
||||
..
|
||||
.. Here `-i` argument is define a second time in same Config:
|
||||
..
|
||||
.. .. literalinclude:: src/proxy.py
|
||||
.. :lines: 17-18
|
||||
.. :linenos:
|
||||
.. :name: Proxy5
|
||||
..
|
||||
.. But `http_ip_address` and `auto_config_url` are not accessible together:
|
||||
..
|
||||
.. - `http_ip_address` is visible only if `proxy_mode` is "Manual proxy configuration"
|
||||
.. - `auto_config_url` is only visible when `proxy_mode` is "Automatic proxy configuration URL"
|
||||
..
|
||||
.. Boolean argument
|
||||
.. ===================
|
||||
..
|
||||
.. Boolean option creates two arguments:
|
||||
..
|
||||
.. - --<boolean_name>: it activates (set to True) the option
|
||||
.. - --no-<boolean_name>: it deactivates (set to False) the option
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "No proxy" \
|
||||
.. --dns_over_https
|
||||
.. {'dns_over_https': True,
|
||||
.. 'proxy_mode': 'No proxy'}
|
||||
..
|
||||
.. $ python3 src/proxy.py "No proxy" \
|
||||
.. --no-dns_over_https
|
||||
.. {'dns_over_https': False,
|
||||
.. 'proxy_mode': 'No proxy'}
|
||||
..
|
||||
.. Multi
|
||||
.. =========
|
||||
..
|
||||
.. Some values are multi. So we can set several value for this option.
|
||||
..
|
||||
.. For example, we can set serveral domain (cadoles.com and gnu.org) to "Domain names for which proxy will be desactivated" option:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "Automatic proxy configuration URL" \
|
||||
.. --configuration.automatic_proxy.auto_config_url http://proxy.cadoles.com/proxy.pac \
|
||||
.. --no_proxy.no_proxy_domain cadoles.com gnu.org
|
||||
.. {'configuration.automatic_proxy.auto_config_url': 'http://proxy.cadoles.com/proxy.pac',
|
||||
.. 'configuration.automatic_proxy.i': 'http://proxy.cadoles.com/proxy.pac',
|
||||
.. 'dns_over_https': False,
|
||||
.. 'no_proxy.no_proxy_domain': ['cadoles.com', 'gnu.org'],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_netmask': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_network': [],
|
||||
.. 'proxy_mode': 'Automatic proxy configuration URL'}
|
||||
..
|
||||
.. Leadership
|
||||
.. ============
|
||||
..
|
||||
.. Leadership option are also supported. The leader option is a standard multi option.
|
||||
.. But follower option are not view as a multi option. Follower value are separate and we need to set index to set a follower option.
|
||||
..
|
||||
.. If we want to had two "Network for which proxy will be desactivated":
|
||||
..
|
||||
.. - 192.168.1.1/255.255.255.255
|
||||
.. - 192.168.0.0/255.255.255.0
|
||||
..
|
||||
.. We have to do:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "Automatic proxy configuration URL" \
|
||||
.. --configuration.automatic_proxy.auto_config_url http://proxy.cadoles.com/proxy.pac \
|
||||
.. --no_proxy.no_proxy_network.no_proxy_network 192.168.1.1 192.168.0.0 \
|
||||
.. --no_proxy.no_proxy_network.no_proxy_netmask 0 255.255.255.255 \
|
||||
.. --no_proxy.no_proxy_network.no_proxy_netmask 1 255.255.255.0
|
||||
.. {'configuration.automatic_proxy.auto_config_url': 'http://proxy.cadoles.com/proxy.pac',
|
||||
.. 'configuration.automatic_proxy.i': 'http://proxy.cadoles.com/proxy.pac',
|
||||
.. 'dns_over_https': False,
|
||||
.. 'no_proxy.no_proxy_domain': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_netmask': ['255.255.255.255',
|
||||
.. '255.255.255.0'],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_network': ['192.168.1.1', '192.168.0.0'],
|
||||
.. 'proxy_mode': 'Automatic proxy configuration URL'}
|
||||
..
|
||||
.. We cannot reduce leader lenght:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "Automatic proxy configuration URL" \
|
||||
.. --configuration.automatic_proxy.auto_config_url http://proxy.cadoles.com/proxy.pac \
|
||||
.. --no_proxy.no_proxy_network.no_proxy_network 192.168.1.1 192.168.0.0 \
|
||||
.. --no_proxy.no_proxy_network.no_proxy_netmask 0 255.255.255.255 \
|
||||
.. --no_proxy.no_proxy_network.no_proxy_netmask 1 255.255.255.0 \
|
||||
.. --no_proxy.no_proxy_network.no_proxy_network 192.168.1.1
|
||||
.. usage: proxy.py -i "http://proxy.cadoles.com/proxy.pac" --no_proxy.no_proxy_network.no_proxy_network "192.168.1.1" "192.168.0.0" "Automatic proxy configuration URL"
|
||||
.. [-h] -i AUTO_CONFIG_URL
|
||||
.. [--no_proxy.no_proxy_network.no_proxy_network [NO_PROXY_NETWORK [NO_PROXY_NETWORK ...]]]
|
||||
.. [--no_proxy.no_proxy_network.pop-no_proxy_network INDEX]
|
||||
.. --no_proxy.no_proxy_network.no_proxy_netmask INDEX NO_PROXY_NETMASK
|
||||
.. [--no_proxy.no_proxy_domain [NO_PROXY_DOMAIN [NO_PROXY_DOMAIN ...]]]
|
||||
.. [--dns_over_https] [--no-dns_over_https]
|
||||
.. {No proxy,Manual proxy configuration,Automatic proxy configuration URL}
|
||||
.. proxy.py: error: cannot reduce length of the leader "--no_proxy.no_proxy_network.no_proxy_network"
|
||||
..
|
||||
.. So an argument --pop-<leader> is automatically created. You need to specified index as parameter:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "Automatic proxy configuration URL" \
|
||||
.. --configuration.automatic_proxy.auto_config_url http://proxy.cadoles.com/proxy.pac \
|
||||
.. --no_proxy.no_proxy_network.no_proxy_network 192.168.1.1 192.168.0.0 \
|
||||
.. --no_proxy.no_proxy_network.no_proxy_netmask 0 255.255.255.255 \
|
||||
.. --no_proxy.no_proxy_network.pop-no_proxy_network 1
|
||||
.. {'configuration.automatic_proxy.auto_config_url': 'http://proxy.cadoles.com/proxy.pac',
|
||||
.. 'configuration.automatic_proxy.i': 'http://proxy.cadoles.com/proxy.pac',
|
||||
.. 'dns_over_https': False,
|
||||
.. 'no_proxy.no_proxy_domain': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_netmask': ['255.255.255.255'],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_network': ['192.168.1.1'],
|
||||
.. 'proxy_mode': 'Automatic proxy configuration URL'}
|
||||
..
|
||||
.. Validation
|
||||
.. ===============
|
||||
..
|
||||
.. All arguments are validated successively by argparser and Tiramisu:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "Automatic proxy configuration URL" \
|
||||
.. --configuration.automatic_proxy.auto_config_url cadoles.com
|
||||
.. usage: proxy.py "Automatic proxy configuration URL" [-h] -i AUTO_CONFIG_URL
|
||||
.. [--no_proxy.no_proxy_network.no_proxy_network [NO_PROXY_NETWORK [NO_PROXY_NETWORK ...]]]
|
||||
.. [--no_proxy.no_proxy_network.pop-no_proxy_network INDEX]
|
||||
.. --no_proxy.no_proxy_network.no_proxy_netmask
|
||||
.. INDEX NO_PROXY_NETMASK
|
||||
.. [--no_proxy.no_proxy_domain [NO_PROXY_DOMAIN [NO_PROXY_DOMAIN ...]]]
|
||||
.. [--dns_over_https]
|
||||
.. [--no-dns_over_https]
|
||||
.. {No proxy,Manual proxy
|
||||
.. configuration,Automatic
|
||||
.. proxy configuration URL}
|
||||
.. proxy.py: error: "cadoles.com" is an invalid URL for "Proxy’s auto config URL", must start with http:// or https://
|
||||
..
|
||||
.. In error message, we have the option description ("Proxy's auto config URL") by default.
|
||||
..
|
||||
.. That why we redefined display_name function:
|
||||
..
|
||||
.. .. literalinclude:: src/proxy.py
|
||||
.. :lines: 40-43
|
||||
.. :linenos:
|
||||
.. :name: Proxy6
|
||||
..
|
||||
.. Now we have --<path> as description:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "Automatic proxy configuration URL" \
|
||||
.. --configuration.automatic_proxy.auto_config_url cadoles.com
|
||||
.. usage: proxy.py "Automatic proxy configuration URL" [-h] -i AUTO_CONFIG_URL
|
||||
.. [--no_proxy.no_proxy_network.no_proxy_network [NO_PROXY_NETWORK [NO_PROXY_NETWORK ...]]]
|
||||
.. [--no_proxy.no_proxy_network.pop-no_proxy_network INDEX]
|
||||
.. --no_proxy.no_proxy_network.no_proxy_netmask
|
||||
.. INDEX NO_PROXY_NETMASK
|
||||
.. [--no_proxy.no_proxy_domain [NO_PROXY_DOMAIN [NO_PROXY_DOMAIN ...]]]
|
||||
.. [--dns_over_https]
|
||||
.. [--no-dns_over_https]
|
||||
.. {No proxy,Manual proxy
|
||||
.. configuration,Automatic
|
||||
.. proxy configuration URL}
|
||||
.. proxy.py: error: "cadoles.com" is an invalid URL for "--configuration.automatic_proxy.auto_config_url", must start with http:// or https://
|
||||
..
|
||||
.. Mandatory
|
||||
.. =============
|
||||
..
|
||||
.. Obviously the mandatory options are checked.
|
||||
..
|
||||
.. The positional argument is mandatory, so if we don't set it, an error occured:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py
|
||||
.. usage: proxy.py [-h] [--dns_over_https] [--no-dns_over_https]
|
||||
.. {No proxy,Manual proxy configuration,Automatic proxy
|
||||
.. configuration URL}
|
||||
.. proxy.py: error: the following arguments are required: proxy_mode
|
||||
..
|
||||
.. Others arguments are also check:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "Automatic proxy configuration URL"
|
||||
.. usage: proxy.py "Automatic proxy configuration URL" [-h] -i AUTO_CONFIG_URL
|
||||
.. [--no_proxy.no_proxy_network.no_proxy_network [NO_PROXY_NETWORK [NO_PROXY_NETWORK ...]]]
|
||||
.. [--no_proxy.no_proxy_network.pop-no_proxy_network INDEX]
|
||||
.. --no_proxy.no_proxy_network.no_proxy_netmask
|
||||
.. INDEX NO_PROXY_NETMASK
|
||||
.. [--no_proxy.no_proxy_domain [NO_PROXY_DOMAIN [NO_PROXY_DOMAIN ...]]]
|
||||
.. [--dns_over_https]
|
||||
.. [--no-dns_over_https]
|
||||
.. {No proxy,Manual proxy
|
||||
.. configuration,Automatic
|
||||
.. proxy configuration URL}
|
||||
.. proxy.py: error: the following arguments are required: --configuration.automatic_proxy.auto_config_url
|
||||
..
|
||||
.. Persistence configuration and mandatories validation
|
||||
.. ======================================================
|
||||
..
|
||||
.. First of all, activate persistence configuration and remove mandatory validation:
|
||||
..
|
||||
.. .. literalinclude:: src/proxy_persistent.py
|
||||
.. :lines: 43-46
|
||||
.. :linenos:
|
||||
.. :name: Proxy7
|
||||
..
|
||||
.. We can disabled mandatory validation in parse_args function.
|
||||
..
|
||||
.. .. literalinclude:: src/proxy_persistent.py
|
||||
.. :lines: 51
|
||||
.. :linenos:
|
||||
.. :name: Proxy8
|
||||
..
|
||||
.. In this case, we can store incomplete value:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy_persistent.py 'Manual proxy configuration'
|
||||
.. {'configuration.manual_proxy.http_ip_address': None,
|
||||
.. 'configuration.manual_proxy.http_port': '8080',
|
||||
.. 'configuration.manual_proxy.i': None,
|
||||
.. 'configuration.manual_proxy.p': '8080',
|
||||
.. 'dns_over_https': False,
|
||||
.. 'no_proxy.no_proxy_domain': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_netmask': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_network': [],
|
||||
.. 'proxy_mode': 'Manual proxy configuration'}
|
||||
..
|
||||
.. We can complete configuration after:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy_persistent.py -i 192.168.1.1
|
||||
.. {'configuration.manual_proxy.http_ip_address': '192.168.1.1',
|
||||
.. 'configuration.manual_proxy.http_port': '8080',
|
||||
.. 'configuration.manual_proxy.i': '192.168.1.1',
|
||||
.. 'configuration.manual_proxy.p': '8080',
|
||||
.. 'dns_over_https': False,
|
||||
.. 'no_proxy.no_proxy_domain': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_netmask': [],
|
||||
.. 'no_proxy.no_proxy_network.no_proxy_network': [],
|
||||
.. 'proxy_mode': 'Manual proxy configuration'}
|
||||
..
|
||||
.. When configuration is already set, help command, display already set options is usage ligne.
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy_persistent.py -h
|
||||
.. usage: proxy_persistent.py -i "192.168.1.1" "Manual proxy configuration"
|
||||
.. [..]
|
||||
..
|
||||
.. Description and epilog
|
||||
.. ============================
|
||||
..
|
||||
.. As argparser, description and epilog message can be added to the generated help:
|
||||
..
|
||||
.. .. code-block:: python
|
||||
..
|
||||
.. parser = TiramisuCmdlineParser(proxy_config, description='New description!', epilog='New epilog!')
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py -h
|
||||
.. usage: proxy.py [-h] [--dns_over_https] [--no-dns_over_https]
|
||||
.. {No proxy,Manual proxy configuration,Automatic proxy
|
||||
.. configuration URL}
|
||||
..
|
||||
.. New description!
|
||||
..
|
||||
.. positional arguments:
|
||||
.. {No proxy,Manual proxy configuration,Automatic proxy configuration URL}
|
||||
.. Proxy's config mode
|
||||
..
|
||||
.. optional arguments:
|
||||
.. -h, --help show this help message and exit
|
||||
.. --dns_over_https Enable DNS over HTTPS
|
||||
.. --no-dns_over_https
|
||||
..
|
||||
.. New epilog!
|
||||
..
|
||||
..
|
||||
.. By default, TiramisuCmdlineParser objects line-wrap the description and epilog texts in command-line help messages.
|
||||
..
|
||||
.. If there are line breaks in description or epilog, it automatically replace by a space. You need to change formatter class:
|
||||
..
|
||||
.. .. code-block:: python
|
||||
..
|
||||
.. from argparse import RawDescriptionHelpFormatter
|
||||
.. parser = TiramisuCmdlineParser(proxy_config, description='New description!\nLine breaks', epilog='New epilog!\nLine breaks', formatter_class=RawDescriptionHelpFormatter)
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py -h
|
||||
.. usage: proxy.py [-h] [--dns_over_https] [--no-dns_over_https]
|
||||
.. {No proxy,Manual proxy configuration,Automatic proxy
|
||||
.. configuration URL}
|
||||
..
|
||||
.. New description!
|
||||
.. Line breaks
|
||||
..
|
||||
.. positional arguments:
|
||||
.. {No proxy,Manual proxy configuration,Automatic proxy configuration URL}
|
||||
.. Proxy's config mode
|
||||
..
|
||||
.. optional arguments:
|
||||
.. -h, --help show this help message and exit
|
||||
.. --dns_over_https Enable DNS over HTTPS
|
||||
.. --no-dns_over_https
|
||||
..
|
||||
.. New epilog!
|
||||
.. Line breaks
|
||||
..
|
||||
.. Hide empty optiondescription
|
||||
.. ===============================
|
||||
..
|
||||
.. An empty optiondescription, is an optiondescription without any option (could have others optiondescriptions).
|
||||
..
|
||||
.. For example, configuration is an empty optiondescription:
|
||||
..
|
||||
.. .. literalinclude:: src/proxy.py
|
||||
.. :lines: 23-24
|
||||
.. :linenos:
|
||||
.. :name: Proxy9
|
||||
..
|
||||
.. This optiondescription doesn't appears in help:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "No proxy" -h
|
||||
.. usage: proxy.py "No proxy" [-h] [--dns_over_https] [--no-dns_over_https]
|
||||
.. {No proxy,Manual proxy configuration,Automatic
|
||||
.. proxy configuration URL}
|
||||
..
|
||||
.. positional arguments:
|
||||
.. {No proxy,Manual proxy configuration,Automatic proxy configuration URL}
|
||||
.. Proxy's config mode
|
||||
..
|
||||
.. optional arguments:
|
||||
.. -h, --help show this help message and exit
|
||||
.. --dns_over_https Enable DNS over HTTPS
|
||||
.. --no-dns_over_https
|
||||
..
|
||||
..
|
||||
..
|
||||
.. This behavior is, in fact, due to two conditions:
|
||||
..
|
||||
.. - there is no option
|
||||
.. - there is no description (None)
|
||||
..
|
||||
.. If we add description:
|
||||
..
|
||||
.. .. code-block:: python
|
||||
..
|
||||
.. configuration = OptionDescription('configuration', 'Configuration',
|
||||
.. [manual_proxy, automatic_proxy])
|
||||
..
|
||||
.. This optiondescription is specified in help:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py "No proxy" -h
|
||||
.. usage: proxy.py "No proxy" [-h] [--dns_over_https] [--no-dns_over_https]
|
||||
.. {No proxy,Manual proxy configuration,Automatic
|
||||
.. proxy configuration URL}
|
||||
..
|
||||
.. positional arguments:
|
||||
.. {No proxy,Manual proxy configuration,Automatic proxy configuration URL}
|
||||
.. Proxy's config mode
|
||||
..
|
||||
.. optional arguments:
|
||||
.. -h, --help show this help message and exit
|
||||
.. --dns_over_https Enable DNS over HTTPS
|
||||
.. --no-dns_over_https
|
||||
..
|
||||
.. configuration:
|
||||
.. Configuration
|
||||
..
|
||||
.. If you don't want empty optiondescription even if there is a description, you could add remove_empty_od to True in parse_args function:
|
||||
..
|
||||
.. .. code-block:: python
|
||||
..
|
||||
.. parser = TiramisuCmdlineParser(proxy_config, remove_empty_od=True)
|
||||
..
|
||||
.. SubConfig
|
||||
.. ================
|
||||
..
|
||||
.. Entire Config is transformed into an argument by default.
|
||||
..
|
||||
.. It could be interesting to display only 'configuration' OptionDescription.
|
||||
..
|
||||
.. To do this, we have to define default all mandatories options outside this scope:
|
||||
..
|
||||
.. .. code-block:: python
|
||||
..
|
||||
.. proxy_mode = ChoiceOption('proxy_mode', 'Proxy\'s config mode', ('No proxy',
|
||||
.. 'Manual proxy configuration',
|
||||
.. 'Automatic proxy configuration URL'),
|
||||
.. default='Manual proxy configuration',
|
||||
.. properties=('positional', 'mandatory'))
|
||||
..
|
||||
.. Finally specified the root argument to `TiramisuCmdlineParser`:
|
||||
..
|
||||
.. .. code-block:: python
|
||||
..
|
||||
.. parser = TiramisuCmdlineParser(proxy_config, root='configuration')
|
||||
..
|
||||
.. Now, only sub option of configuration is proposed:
|
||||
..
|
||||
.. .. code-block:: bash
|
||||
..
|
||||
.. $ python3 src/proxy.py -h
|
||||
.. usage: proxy.py [-h] -i HTTP_IP_ADDRESS -p [HTTP_PORT]
|
||||
..
|
||||
.. optional arguments:
|
||||
.. -h, --help show this help message and exit
|
||||
..
|
||||
.. configuration.manual_proxy:
|
||||
.. Manual proxy settings
|
||||
..
|
||||
.. -i HTTP_IP_ADDRESS, --configuration.manual_proxy.http_ip_address HTTP_IP_ADDRESS
|
||||
.. Proxy's HTTP IP
|
||||
.. -p [HTTP_PORT], --configuration.manual_proxy.http_port [HTTP_PORT]
|
||||
.. Proxy's HTTP Port
|
||||
..
|
149
docs/conf.py
Normal file
|
@ -0,0 +1,149 @@
|
|||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file does only contain a selection of the most common options. For a
|
||||
# full list see the documentation:
|
||||
# http://www.sphinx-doc.org/en/master/config
|
||||
|
||||
# -- Path setup --------------------------------------------------------------
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
# import os
|
||||
# import sys
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'Tiramisu'
|
||||
copyright = '2011-2023, Silique'
|
||||
author = 'egarette'
|
||||
|
||||
# The short X.Y version
|
||||
version = ''
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = '4.0'
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#
|
||||
# needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
|
||||
extensions = [
|
||||
'sphinx.ext.extlinks', 'sphinx_lesson',
|
||||
#'myst_parser', 'sphinx.ext.extlinks'
|
||||
]
|
||||
#
|
||||
#myst_enable_extensions = [
|
||||
# "amsmath",
|
||||
# "attrs_inline",
|
||||
# "colon_fence",
|
||||
# "deflist",
|
||||
# "dollarmath",
|
||||
# "fieldlist",
|
||||
# "html_admonition",
|
||||
# "html_image",
|
||||
## "linkify",
|
||||
# "replacements",
|
||||
# "smartquotes",
|
||||
# "strikethrough",
|
||||
# "substitution",
|
||||
# "tasklist",
|
||||
#]
|
||||
|
||||
|
||||
# **extlinks** 'sphinx.ext.extlinks',
|
||||
# enables syntax like :proxy:`my source <hello>` in the src files
|
||||
extlinks = {'proxy': ('/proxy/%s.html',
|
||||
'external link: ')}
|
||||
|
||||
default_role = "code"
|
||||
|
||||
html_theme = "sphinx_rtd_theme"
|
||||
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
html_short_title = "Tiramisu"
|
||||
html_title = "Tiramisu documenation"
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
html_show_sourcelink = False
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
html_show_sphinx = False
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
html_show_copyright = True
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
#
|
||||
#source_suffix = ['.rst', '.md']
|
||||
source_suffix = '.rst'
|
||||
#source_suffix = {
|
||||
# '.rst': 'restructuredtext',
|
||||
# '.txt': 'restructuredtext',
|
||||
# '.md': 'markdown',
|
||||
#}
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = 'en'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path.
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = None
|
||||
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
#html_theme = 'alabaster'
|
||||
# **themes**
|
||||
#html_theme = 'bizstyle'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#
|
||||
# html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# Custom sidebar templates, must be a dictionary that maps document names
|
||||
# to template names.
|
||||
#
|
||||
# The default sidebars (for documents that don't match any pattern) are
|
||||
# defined by theme itself. Builtin themes are using these templates by
|
||||
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
|
||||
# 'searchbox.html']``.
|
||||
#
|
||||
# html_sidebars = {}
|
||||
|
||||
def setup(app):
|
||||
app.add_css_file('css/custom.css')
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
157
docs/config.rst
Normal file
|
@ -0,0 +1,157 @@
|
|||
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
|
||||
|
||||
Commands:
|
||||
description Get option description
|
||||
dict Convert config and option to tiramisu format
|
||||
get Get Tiramisu option
|
||||
has_dependency Test if option has dependency
|
||||
isdynamic Test if option is a dynamic optiondescription
|
||||
isleadership Test if option is a leader or a follower
|
||||
isoptiondescription Test if option is an optiondescription
|
||||
list List options (by default list only option)
|
||||
name Get option name
|
||||
option Select an option by path
|
||||
path Get option path
|
||||
type Get de option type
|
||||
updates Updates value with tiramisu format
|
||||
|
||||
Then let's print our :class:`Option` details.
|
||||
|
||||
.. literalinclude:: src/getting_started.py
|
||||
:lines: 17
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
Manage selected option
|
||||
|
||||
Commands:
|
||||
dependencies Get dependencies from this option
|
||||
description Get option description
|
||||
dict Convert config and option to tiramisu format
|
||||
extra Get de option extra
|
||||
followers Get the followers option for a leadership
|
||||
get Get Tiramisu option
|
||||
group_type Get type for an optiondescription (only for optiondescription)
|
||||
has_dependency Test if option has dependency
|
||||
identifiers Get identifiers for dynamic option
|
||||
index Get index of option
|
||||
isdynamic Test if option is a dynamic optiondescription
|
||||
isfollower Test if option is a follower
|
||||
isleader Test if option is a leader
|
||||
isleadership Test if option is a leader or a follower
|
||||
ismulti Test if option could have multi value
|
||||
isoptiondescription Test if option is an optiondescription
|
||||
issubmulti Test if option could have submulti value
|
||||
issymlinkoption Test if option is a symlink option
|
||||
leader Get the leader option for a leadership or a follower option
|
||||
list List options inside an option description (by default list only option)
|
||||
name Get option name
|
||||
option For OptionDescription get sub option, for symlinkoption get the linked option
|
||||
path Get option path
|
||||
pattern Get the option pattern
|
||||
type Get de option type
|
||||
updates Updates value with tiramisu format
|
||||
|
||||
Finaly, let's print the :class:`Config`.
|
||||
|
||||
.. literalinclude:: src/getting_started.py
|
||||
:lines: 19
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
<Config path=None>
|
||||
|
||||
: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
|
||||
|
65
docs/dynoptiondescription.rst
Normal file
|
@ -0,0 +1,65 @@
|
|||
==========================================================
|
||||
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
|
||||
|
||||
* - identifiers
|
||||
- Identifiers is a :doc:`calculation` that return the list of identifiers used to create dynamic option description.
|
||||
|
||||
* - properties
|
||||
- A list of :doc:`property` (inside a frozenset().
|
||||
|
||||
* - informations
|
||||
- We can add default informations to this option description.
|
||||
|
||||
* - group_type
|
||||
- Type for this group.
|
||||
|
||||
Example
|
||||
==============
|
||||
|
||||
Let's try:
|
||||
|
||||
>>> from tiramisu import StrOption, DynOptionDescription, Calculation
|
||||
>>> def return_identifiers():
|
||||
... return ['1', '2']
|
||||
>>> child1 = StrOption('first', 'First basic option ')
|
||||
>>> child2 = StrOption('second', 'Second basic option ')
|
||||
>>> DynOptionDescription('basic ',
|
||||
... 'Basic options ',
|
||||
... [child1, child2],
|
||||
... Calculation(return_identifiers))
|
||||
|
||||
This example will construct:
|
||||
|
||||
- Basic options 1:
|
||||
|
||||
- First basic option 1
|
||||
- Second basic option 1
|
||||
|
||||
- Basic options 2:
|
||||
|
||||
- First basic option 2
|
||||
- Second basic option 2
|
|
@ -1,6 +1,9 @@
|
|||
# Getting started
|
||||
==================================
|
||||
Getting started
|
||||
==================================
|
||||
|
||||
## What is options handling ?
|
||||
What is options handling?
|
||||
=================================
|
||||
|
||||
Due to more and more available options required to set up an operating system,
|
||||
compiler options or whatever, it became quite annoying to hand the necessary
|
||||
|
@ -9,7 +12,8 @@ options.
|
|||
|
||||
To circumvent these problems the configuration control was introduced.
|
||||
|
||||
## What is Tiramisu ?
|
||||
What is Tiramisu?
|
||||
===================
|
||||
|
||||
Tiramisu is an options handler and an options controller, which aims at
|
||||
producing flexible and fast options access. The main advantages are its access
|
||||
|
@ -19,24 +23,35 @@ There is of course type and structure validations, but also
|
|||
validations towards the whole options. Furthermore, options can be reached and
|
||||
changed according to the access rules from nearly everywhere.
|
||||
|
||||
### Installation
|
||||
Installation
|
||||
-------------
|
||||
|
||||
The best way is to use the python [pip](https://pip.pypa.io/en/stable/installing/) installer
|
||||
The best way is to use the python pip_ installer
|
||||
|
||||
.. _pip: https://pip.pypa.io/en/stable/installing/
|
||||
|
||||
And then type:
|
||||
|
||||
```bash
|
||||
$ pip install tiramisu
|
||||
```
|
||||
.. code-block:: bash
|
||||
|
||||
### Advanced users
|
||||
pip install tiramisu
|
||||
|
||||
Advanced users
|
||||
==============
|
||||
|
||||
.. _gettingtiramisu:
|
||||
|
||||
- the library's development homepage is there_
|
||||
|
||||
.. _there: https://forge.cloud.silique.fr/stove/tiramisu/
|
||||
|
||||
To obtain a copy of the sources, check it out from the repository using `git`.
|
||||
We suggest using `git` if one wants to access to the current developments.
|
||||
|
||||
```bash
|
||||
$ git clone https://framagit.org/tiramisu/tiramisu.git
|
||||
```
|
||||
.. code-block:: bash
|
||||
|
||||
git clone https://forge.cloud.silique.fr/stove/tiramisu.git
|
||||
|
||||
This will get you a fresh checkout of the code repository in a local directory
|
||||
named "tiramisu".
|
||||
named ``tiramisu``.
|
||||
|
84
docs/glossary.rst
Normal file
|
@ -0,0 +1,84 @@
|
|||
.. default-role:: literal
|
||||
|
||||
Glossary
|
||||
==========
|
||||
|
||||
.. glossary::
|
||||
|
||||
configuration
|
||||
|
||||
Global configuration object, wich contains the whole configuration
|
||||
options *and* their descriptions (option types and group)
|
||||
|
||||
schema
|
||||
option description
|
||||
|
||||
see :class:`tiramisu.option.OptionDescription`
|
||||
|
||||
The schema of a configuration :
|
||||
|
||||
- the option types
|
||||
|
||||
- how they are organised in groups or even subgroups, that's why we
|
||||
call them **groups** too.
|
||||
|
||||
configuration option
|
||||
|
||||
An option object wich has a name and a value and can be accessed
|
||||
from the configuration object
|
||||
|
||||
access rules
|
||||
|
||||
Global access rules are : :meth:`~config.CommonConfig.read_write()` or
|
||||
:meth:`~config.Config.read_only()`
|
||||
|
||||
default value
|
||||
|
||||
Default value of a configuration option. The default value can be
|
||||
set at instanciation time, or even at any moment. Remember that if
|
||||
you reset the default value, the owner reset to `default`
|
||||
|
||||
freeze
|
||||
|
||||
A whole configuration can be frozen (used in read only access).
|
||||
|
||||
A single option can be frozen too.
|
||||
|
||||
value owner
|
||||
|
||||
When an option is modified, including at the instanciation, we
|
||||
always know who has modified it. It's the owner of the option.
|
||||
|
||||
properties
|
||||
|
||||
an option with properties is a option wich has property like 'hidden' or 'disabled' is an option
|
||||
wich has restricted acces rules.
|
||||
|
||||
hidden option
|
||||
|
||||
a hidden option has a different behaviour on regards to the access
|
||||
of the value in the configuration.
|
||||
|
||||
disabled option
|
||||
|
||||
a disabled option has a different behaviour on regards to the access
|
||||
of the value in the configuration.
|
||||
|
||||
mandatory option
|
||||
|
||||
A mandatory option is a configuration option wich value has to be
|
||||
set, that is the default value cannot be `None`.
|
||||
|
||||
consistency
|
||||
|
||||
Preserving the consistency in a whole configuration is a tricky thing,
|
||||
tiramisu takes care of it for you.
|
||||
|
||||
context
|
||||
|
||||
The context is a :class:`tiramisu.setting.Setting()` object in the
|
||||
configuration that enables us to access to the global properties
|
||||
|
||||
for example the `read_write` or `read_only` :term:`access rules`
|
||||
|
||||
|
BIN
docs/images/firefox_preferences.png
Normal file
After Width: | Height: | Size: 81 KiB |
|
@ -1,36 +1,49 @@
|
|||
![Logo Tiramisu](../logo.png "logo Tiramisu")
|
||||
.. default-role:: literal
|
||||
|
||||
# Python3 Tiramisu library user documentation
|
||||
.. meta::
|
||||
|
||||
## The tasting of `Tiramisu` --- `user documentation`
|
||||
:description: python tiramisu library user documentation
|
||||
:keywords: python, tiramisu, tutorial
|
||||
|
||||
Tiramisu:
|
||||
.. title:: Tiramisu
|
||||
|
||||
The tasting of `Tiramisu` --- `user documentation`
|
||||
===================================================
|
||||
|
||||
.. image:: logo.png
|
||||
:height: 150px
|
||||
|
||||
`Tiramisu`
|
||||
|
||||
- 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::
|
||||
:maxdepth: 2
|
||||
|
||||
gettingstarted
|
||||
config
|
||||
browse
|
||||
api_value
|
||||
api_property
|
||||
storage
|
||||
application
|
||||
quiz
|
||||
glossary
|
||||
|
||||
External project:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
cmdline_parser
|
||||
.. External project:
|
||||
..
|
||||
.. .. toctree::
|
||||
.. :maxdepth: 2
|
||||
..
|
||||
.. cmdline_parser
|
||||
|
||||
.. FIXME ca veut rien dire : "AssertionError: type <class 'tiramisu.autolib.Calculation'> invalide pour des propriétés pour protocols, doit être un frozenset"
|
||||
|
51
docs/leadership.rst
Normal file
|
@ -0,0 +1,51 @@
|
|||
==========================================================
|
||||
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().
|
||||
|
||||
* - informations
|
||||
- We can add default informations to this option description.
|
||||
|
||||
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 |
139
docs/option.rst
Normal file
|
@ -0,0 +1,139 @@
|
|||
==================================
|
||||
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.
|
||||
|
||||
* - 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`.
|
||||
|
||||
* - multi
|
||||
- There are cases where it can be interesting to have a list of values rather than just one.
|
||||
|
||||
* - validators
|
||||
- A list of :doc:`validator`.
|
||||
|
||||
* - properties
|
||||
- A list of :doc:`property` (inside a frozenset().
|
||||
|
||||
* - warnings_only
|
||||
- Only emit warnings if not type validation is invalid.
|
||||
|
||||
* - informations
|
||||
- We can add default informations to this option.
|
||||
|
||||
|
||||
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)
|
44
docs/optiondescription.rst
Normal file
|
@ -0,0 +1,44 @@
|
|||
==============================================
|
||||
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().
|
||||
|
||||
* - informations
|
||||
- We can add default informations to this option description.
|
||||
|
||||
* - group_type
|
||||
- Type for this group.
|
||||
|
||||
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])
|
351
docs/options.rst
Normal file
|
@ -0,0 +1,351 @@
|
|||
==================================
|
||||
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
|
||||
- allow_protocol: allow to define protocol in value, it should be something like tcp:80 or udp:53
|
||||
|
||||
* - MACOption
|
||||
- MAC address for a network card.
|
||||
|
||||
|
||||
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
|
||||
- Extra parameters
|
||||
|
||||
* - 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:
|
||||
-
|
||||
- min_len: minimum length autorise for a password
|
||||
- max_len: maximum length autorise for a passwword
|
||||
- forbidden_char: list of forbidden characters for a password
|
||||
|
||||
* - FilenameOption
|
||||
- For this option, only lowercase and uppercas ASCII character, "-", ".", "_", "~", and "/" are allowed.
|
||||
-
|
||||
- allow_relative: filename should starts with "/" (something like /etc/passwd), we can, with this option to allow relative name
|
||||
- test_existence: file or directory should exists
|
||||
- types
|
||||
- file: it should be a file
|
||||
- directory: it should be a directory
|
||||
|
||||
* - PermissionsOption
|
||||
- Permissions for Unix file. It could be something like 644 or 1644.
|
||||
-
|
||||
|
||||
>>> 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
|
123
docs/own_option.rst
Normal file
|
@ -0,0 +1,123 @@
|
|||
======================================
|
||||
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
|
||||
- _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-10
|
||||
: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 your 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
|
||||
|
||||
Here an example to a lipogram option:
|
||||
|
||||
.. literalinclude:: src/own_option2.py
|
||||
:lines: 3-12
|
||||
: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: 13-17
|
||||
: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: 19-26
|
||||
: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: 28-40
|
||||
: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("bool").help()
|
||||
# the config's __repr__
|
||||
print(cfg)
|
10
docs/src/own_option.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import re
|
||||
from tiramisu import RegexpOption
|
||||
|
||||
|
||||
class VowelOption(RegexpOption):
|
||||
__slots__ = tuple()
|
||||
_type = 'vowel'
|
||||
_regexp = re.compile(r"^[aeiouy]*$")
|
40
docs/src/own_option2.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from tiramisu import Option
|
||||
|
||||
|
||||
class LipogramOption(Option):
|
||||
__slots__ = tuple()
|
||||
_type = '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:
|
1379
locale/fr/LC_MESSAGES/tiramisu.po
Normal file
823
locale/tiramisu.pot
Normal file
|
@ -0,0 +1,823 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: 2024-11-05 08:52+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
|
||||
|
||||
#: tiramisu/api.py:79
|
||||
msgid "Settings:"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/api.py:83
|
||||
msgid "Access to option without verifying permissive properties"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/api.py:88
|
||||
msgid "Access to option without property restriction"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/api.py:93
|
||||
msgid "Do not warnings during validation"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/api.py:97
|
||||
msgid "Commands:"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/api.py:111 tiramisu/api.py:1840
|
||||
msgid "please specify a valid sub function ({0}.{1})"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/api.py:194
|
||||
msgid "please do not specify index ({0}.{1})"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/api.py:199 tiramisu/api.py:844
|
||||
msgid "please specify index with a follower option ({0}.{1})"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/api.py:220
|
||||
msgid "please specify a valid sub function ({0}.{1}): {2}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/api.py:431
|
||||
msgid "the option {0} is not a dynamic option, cannot get identifiers with only_self parameter to True"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/api.py:517
|
||||
msgid "cannot get option from a follower symlink without index"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/api.py:592
|
||||
msgid "cannot add this property: \"{0}\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/api.py:619
|
||||
msgid "cannot remove option's property \"{0}\", use permissive instead in option \"{1}\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/api.py:623
|
||||
msgid "cannot find \"{0}\" in option \"{1}\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/api.py:628
|
||||
msgid "cannot remove option's property \"{0}\", use permissive instead in option \"{1}\" at index \"{2}\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/api.py:632
|
||||
msgid "cannot find \"{0}\" in option \"{1}\" at index \"{2}\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/api.py:676
|
||||
msgid "cannot find \"{0}\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/api.py:808
|
||||
msgid "cannot reduce length of the leader {}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/api.py:861
|
||||
msgid "only multi value has defaultmulti"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/api.py:1020
|
||||
msgid "please specify a valid sub function ({0}.{1}) for {2}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/api.py:1407
|
||||
msgid "properties must be a frozenset"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/api.py:1411 tiramisu/api.py:1438
|
||||
msgid "unknown when {} (must be in append or remove)"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/api.py:1424 tiramisu/api.py:1448 tiramisu/config.py:1680
|
||||
msgid "unknown type {}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/api.py:1812
|
||||
msgid "do not use unrestraint, nowarnings or forcepermissive together"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/autolib.py:80
|
||||
msgid "args in params must be a tuple"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/autolib.py:83 tiramisu/autolib.py:88
|
||||
msgid "arg in params must be a Param"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/autolib.py:85
|
||||
msgid "kwargs in params must be a dict"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/autolib.py:113
|
||||
msgid "paramoption needs an option not {}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/autolib.py:119
|
||||
msgid "param must have a boolean not a {} for notraisepropertyerror"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/autolib.py:122
|
||||
msgid "param must have a boolean not a {} for raisepropertyerror"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/autolib.py:212
|
||||
msgid "option in ParamInformation cannot be a symlinkoption"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/autolib.py:215
|
||||
msgid "option in ParamInformation cannot be a follower"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/autolib.py:218
|
||||
msgid "option in ParamInformation cannot be a dynamic option"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/autolib.py:279
|
||||
msgid "first argument ({0}) must be a function"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/autolib.py:283
|
||||
msgid "help_function ({0}) must be a function"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/autolib.py:452 tiramisu/autolib.py:514
|
||||
msgid "unable to carry out a calculation for {}, {}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/autolib.py:461 tiramisu/autolib.py:521
|
||||
msgid "the option {0} is used in a calculation but is invalid ({1})"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/autolib.py:477 tiramisu/autolib.py:535 tiramisu/autolib.py:584
|
||||
msgid "unable to get value for calculating {0}, {1}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/autolib.py:601
|
||||
msgid "option {0} is not a dynoptiondescription or in a dynoptiondescription"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/autolib.py:848
|
||||
msgid "the \"{}\" function with positional arguments \"{}\" and keyword arguments \"{}\" must not return a list (\"{}\") for the follower option {}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/autolib.py:863
|
||||
msgid "the \"{}\" function must not return a list (\"{}\") for the follower option {}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/autolib.py:904
|
||||
msgid "unexpected error \"{0}\" in function \"{1}\" with arguments \"{3}\" and \"{4}\" for option {2}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/autolib.py:915
|
||||
msgid "unexpected error \"{0}\" in function \"{1}\" for option {2}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/config.py:419
|
||||
msgid "index \"{0}\" is greater than the leadership length \"{1}\" for option {2}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/config.py:579
|
||||
msgid "there is no option description for this config (may be GroupConfig)"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/config.py:668
|
||||
msgid "no option found in config with these criteria"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/config.py:871
|
||||
msgid "the follower option {0} has greater length ({1}) than the leader length ({2})"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/config.py:982 tiramisu/option/optiondescription.py:74
|
||||
msgid "option description seems to be part of an other config"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/config.py:1144
|
||||
msgid "parent of {0} not already exists"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/config.py:1191
|
||||
msgid "cannot set leadership object has root optiondescription"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/config.py:1194
|
||||
msgid "cannot set dynoptiondescription object has root optiondescription"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/config.py:1246
|
||||
msgid "config name must be uniq in groupconfig for \"{0}\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/config.py:1457
|
||||
msgid "unknown config \"{}\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/config.py:1482
|
||||
msgid "child must be a Config, MixConfig or MetaConfig"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/config.py:1517
|
||||
msgid "force_default, force_default_if_same or force_dont_change_value cannot be set with only_config"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/config.py:1527
|
||||
msgid "force_default and force_dont_change_value cannot be set together"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/config.py:1676
|
||||
msgid "config name must be uniq in groupconfig for {0}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/config.py:1721
|
||||
msgid "config added has no name, the name is mandatory"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/config.py:1726
|
||||
msgid "config name \"{0}\" is not uniq in groupconfig \"{1}\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/config.py:1744 tiramisu/config.py:1750
|
||||
msgid "cannot find the config {0}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/config.py:1776
|
||||
msgid "MetaConfig with optiondescription must have string has child, not {}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/config.py:1788
|
||||
msgid "child must be a Config or MetaConfig"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/config.py:1793
|
||||
msgid "all config in metaconfig must have the same optiondescription"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/config.py:1810
|
||||
msgid "metaconfig must have the same optiondescription"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/error.py:31
|
||||
msgid "and"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/error.py:33
|
||||
msgid "or"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/error.py:55
|
||||
msgid " {} "
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/error.py:108
|
||||
msgid "property"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/error.py:110
|
||||
msgid "properties"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/error.py:113
|
||||
msgid "cannot modify the {0} {1} because \"{2}\" has {3} {4}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/error.py:115
|
||||
msgid "cannot modify the {0} {1} because has {2} {3}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/error.py:118
|
||||
msgid "cannot access to {0} {1} because \"{2}\" has {3} {4}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/error.py:120
|
||||
msgid "cannot access to {0} {1} because has {2} {3}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/error.py:192
|
||||
msgid "invalid value"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/error.py:201
|
||||
msgid "attention, \"{0}\" could be an invalid {1} for \"{2}\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/error.py:219 tiramisu/error.py:228
|
||||
msgid "\"{0}\" is an invalid {1} for \"{2}\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/function.py:65
|
||||
msgid "network \"{0}\" ({1}) does not match with this netmask"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/function.py:83
|
||||
msgid "IP \"{0}\" ({1}) with this netmask is in fact a network address"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/function.py:88
|
||||
msgid "IP \"{0}\" ({1}) with this netmask is in fact a broadcast address"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/function.py:106
|
||||
msgid "broadcast invalid with network {0} ({1}) and netmask {2} ({3})"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/function.py:134
|
||||
msgid "this IP is not in network {network[\"value\"]} ({network[\"name\"]})"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/function.py:136
|
||||
msgid "this IP is not in network {network[\"value\"]} ({network[\"name\"]}) with netmask {netmask[\"value\"]} ({netmask[\"name\"]})"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/function.py:143
|
||||
msgid "this IP with the network {0} ({1}) is in fact a network address"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/function.py:148
|
||||
msgid "this IP with the network {0} ({1}) is in fact a broadcast address"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/function.py:165
|
||||
msgid "value is identical to {0}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/function.py:400
|
||||
msgid "unexpected value in calc_value with join attribute \"{0}\" with invalid length \"{1}\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/function.py:527
|
||||
msgid "unexpected {0} condition_operator in calc_value"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/function.py:591
|
||||
msgid "unexpected condition_{0} must have \"todict\" argument"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/function.py:602
|
||||
msgid "the value of \"{0}\" is {1}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/function.py:604
|
||||
msgid "the value of \"{0}\" is not {1}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/baseoption.py:75 tiramisu/option/symlinkoption.py:44
|
||||
msgid "\"{0}\" is an invalid name for an option"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/baseoption.py:88
|
||||
msgid "invalid properties type {0} for {1}, must be a frozenset"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/baseoption.py:98
|
||||
msgid "invalid property type {0} for {1}, must be a string or a Calculation"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/baseoption.py:249
|
||||
msgid "information's item for {0} not found: \"{1}\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/baseoption.py:267
|
||||
msgid "'{0}' ({1}) object attribute '{2}' is read-only"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/baseoption.py:308
|
||||
msgid "\"{}\" ({}) object attribute \"{}\" is read-only"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/baseoption.py:320
|
||||
msgid "{0} not part of any Config"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/broadcastoption.py:41
|
||||
msgid "invalid string"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/choiceoption.py:47
|
||||
msgid "values must be a tuple or a calculation for {0}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/choiceoption.py:70
|
||||
msgid "the calculated values \"{0}\" for \"{1}\" is not a list"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/choiceoption.py:101
|
||||
msgid "only \"{0}\" is allowed"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/choiceoption.py:103
|
||||
msgid "only {0} are allowed"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/domainnameoption.py:60
|
||||
msgid "unknown type {0} for hostname"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/domainnameoption.py:63
|
||||
msgid "allow_ip must be a boolean"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/domainnameoption.py:65
|
||||
msgid "allow_cidr_network must be a boolean"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/domainnameoption.py:67
|
||||
msgid "allow_without_dot must be a boolean"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/domainnameoption.py:69
|
||||
msgid "allow_startswith_dot must be a boolean"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/domainnameoption.py:81
|
||||
msgid "must start with lowercase characters followed by lowercase characters, number, \"-\" and \".\" characters are allowed"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/domainnameoption.py:84
|
||||
msgid "must start with lowercase characters followed by lowercase characters, number, \"-\" and \".\" characters are recommanded"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/domainnameoption.py:88
|
||||
#: tiramisu/option/domainnameoption.py:89
|
||||
msgid "could be a IP, otherwise {}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/domainnameoption.py:134
|
||||
msgid "invalid length (min 1)"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/domainnameoption.py:137
|
||||
msgid "invalid length (max {0})"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/domainnameoption.py:143
|
||||
msgid "must have dot"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/domainnameoption.py:145
|
||||
msgid "invalid length (max 255)"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/domainnameoption.py:163
|
||||
msgid "must not be an IP"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/domainnameoption.py:186
|
||||
msgid "some characters are uppercase"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/dynoptiondescription.py:131
|
||||
msgid "DynOptionDescription identifiers for option {0}, is not a list ({1})"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/dynoptiondescription.py:142
|
||||
msgid "invalid identifier \"{}\" for option {}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/dynoptiondescription.py:150
|
||||
msgid "DynOptionDescription \"{0}\" identifiers return a list with same values \"{1}\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/filenameoption.py:47
|
||||
msgid "types parameter must be a list, not \"{0}\" for \"{1}\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/filenameoption.py:67
|
||||
msgid "must starts with \"/\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/filenameoption.py:78
|
||||
msgid "cannot find {0} \"{1}\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/intoption.py:52
|
||||
msgid "value should be equal or greater than \"{0}\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/intoption.py:54
|
||||
msgid "value must be equal or greater than \"{0}\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/intoption.py:59
|
||||
msgid "value should be less than \"{0}\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/intoption.py:61
|
||||
msgid "value must be less than \"{0}\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/ipoption.py:57
|
||||
msgid "it's in fact a network address"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/ipoption.py:59
|
||||
msgid "it's in fact a broacast address"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/ipoption.py:71
|
||||
msgid "CIDR address must have a \"/\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/ipoption.py:80
|
||||
msgid "shouldn't be reserved IP"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/ipoption.py:82
|
||||
msgid "mustn't be reserved IP"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/ipoption.py:86
|
||||
msgid "should be private IP"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/ipoption.py:88
|
||||
msgid "must be private IP"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/leadership.py:55
|
||||
msgid "cannot set \"group_type\" attribute for a Leadership"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/leadership.py:67
|
||||
msgid "a leader and a follower are mandatories in leadership \"{}\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/leadership.py:89
|
||||
msgid "leader cannot have \"{}\" property"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/leadership.py:101
|
||||
msgid "leadership {0} shall not have a symlinkoption"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/leadership.py:108
|
||||
msgid "leadership {0} shall not have a subgroup"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/leadership.py:114
|
||||
msgid "only multi option allowed in leadership {0} but option {1} is not a multi"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/leadership.py:141
|
||||
msgid "not allowed default value for follower option {0} in leadership {1}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/networkoption.py:45
|
||||
msgid "must use CIDR notation"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/networkoption.py:60
|
||||
msgid "shouldn't be reserved network"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/networkoption.py:62
|
||||
msgid "mustn't be reserved network"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/option.py:73
|
||||
msgid "default_multi is set whereas multi is False in option: {0}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/option.py:93
|
||||
msgid "invalid multi type \"{}\" for \"{}\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/option.py:112
|
||||
msgid "validators must be a list of Calculation for \"{0}\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/option.py:117
|
||||
msgid "validators must be a Calculation for \"{0}\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/option.py:146
|
||||
msgid "invalid default_multi value \"{0}\" for option {1}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/option.py:154
|
||||
msgid "invalid default_multi value \"{0}\" for option {1}, {2}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/option.py:167
|
||||
msgid "invalid default_multi value \"{0}\" for option {1}, must be a list for a submulti"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/option.py:290
|
||||
msgid "the value \"{}\" is not unique"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/option.py:352
|
||||
msgid "which must not be a list"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/option.py:404 tiramisu/option/option.py:430
|
||||
msgid "which must be a list"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/option.py:424
|
||||
msgid "which \"{}\" must be a list of list"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/optiondescription.py:109
|
||||
msgid "duplicate option: {0}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/optiondescription.py:244
|
||||
msgid "unknown option \"{0}\" in root optiondescription (it's a dynamic option)"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/optiondescription.py:279
|
||||
msgid "unknown option \"{0}\" in root optiondescription"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/optiondescription.py:282
|
||||
msgid "unknown option \"{0}\" in optiondescription {1}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/optiondescription.py:338
|
||||
msgid "children in optiondescription \"{}\" must be a list"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/optiondescription.py:366
|
||||
msgid "duplicate option name: \"{0}\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/optiondescription.py:372
|
||||
msgid "the option's name \"{0}\" start as the dynoptiondescription's name \"{1}\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/optiondescription.py:415
|
||||
msgid "cannot change group_type if already set (old {0}, new {1})"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/optiondescription.py:420
|
||||
msgid "group_type: {0} not allowed"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/passwordoption.py:49
|
||||
msgid "at least {0} characters are required"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/passwordoption.py:52
|
||||
msgid "maximum {0} characters required"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/passwordoption.py:57
|
||||
msgid "must not have the characters {0}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/permissionsoption.py:52
|
||||
msgid "only 3 or 4 octal digits are allowed"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/permissionsoption.py:63
|
||||
msgid "user"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/permissionsoption.py:64
|
||||
#: tiramisu/option/permissionsoption.py:66
|
||||
msgid "group"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/permissionsoption.py:67
|
||||
msgid "other"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/permissionsoption.py:68
|
||||
msgid "{0} has more right than {1}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/permissionsoption.py:71
|
||||
msgid "too weak"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/portoption.py:74
|
||||
msgid "inconsistency in allowed range"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/portoption.py:79
|
||||
msgid "max value is empty"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/portoption.py:92
|
||||
msgid "range must have two values only"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/portoption.py:95
|
||||
msgid "first port in range must be smaller than the second one"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/portoption.py:121
|
||||
msgid "should be between {0} and {1}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/portoption.py:123
|
||||
msgid "must be between {0} and {1}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/symlinkoption.py:51
|
||||
msgid "malformed symlink second parameters must be an option for \"{0}\", not {1}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/urloption.py:91
|
||||
msgid "must start with http:// or https://"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/option/urloption.py:119
|
||||
msgid "must ends with a valid resource name"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/setting.py:255
|
||||
msgid "can't rebind {0}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/setting.py:262
|
||||
msgid "can't unbind {0}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/setting.py:464
|
||||
msgid "invalid property type {type(new_prop)} for {subconfig.option.impl_getname()} with {prop.function.__name__} function"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/setting.py:476
|
||||
msgid "leader cannot have \"{new_prop}\" property"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/setting.py:564
|
||||
msgid "leader cannot have \"{0}\" property"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/setting.py:573
|
||||
msgid "a leader ({0}) cannot have \"force_default_on_freeze\" or \"force_metaconfig_on_freeze\" property without \"frozen\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/setting.py:607
|
||||
msgid "permissive must be a frozenset"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/setting.py:617
|
||||
msgid "cannot add those permissives: {0}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/setting.py:654
|
||||
msgid "can't reset properties to the symlinkoption \"{}\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/setting.py:667
|
||||
msgid "can't reset permissives to the symlinkoption \"{}\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/todict.py:395
|
||||
msgid "option {} only works when remotable is not \"none\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/todict.py:561
|
||||
msgid "unable to transform tiramisu object to dict: {}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/todict.py:876 tiramisu/todict.py:1033
|
||||
msgid "unknown form {}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/todict.py:923
|
||||
msgid "not in current area"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/todict.py:947
|
||||
msgid "only multi option can have action \"add\", but \"{}\" is not a multi"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/todict.py:953
|
||||
msgid "unknown action {}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/value.py:564 tiramisu/value.py:861
|
||||
msgid "set owner \"{0}\" is forbidden"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/value.py:571
|
||||
msgid "\"{0}\" is a default value, so we cannot change owner to \"{1}\""
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/value.py:740
|
||||
msgid "index {index} is greater than the length {length} for option {subconfig.option.impl_get_display_name(with_quote=True)}"
|
||||
msgstr ""
|
||||
|
||||
#: tiramisu/value.py:847
|
||||
msgid "information's item not found \"{}\""
|
||||
msgstr ""
|
||||
|
37
pyproject.toml
Normal file
|
@ -0,0 +1,37 @@
|
|||
[build-system]
|
||||
build-backend = "flit_core.buildapi"
|
||||
requires = ["flit_core >=3.8.0,<4"]
|
||||
|
||||
[project]
|
||||
name = "tiramisu"
|
||||
version = "5.1.0"
|
||||
authors = [{name = "Emmanuel Garette", email = "gnunux@gnunux.info"}]
|
||||
readme = "README.md"
|
||||
description = "an options controller tool"
|
||||
requires-python = ">=3.8"
|
||||
license = {file = "LICENSE"}
|
||||
classifiers = [
|
||||
"License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Operating System :: OS Independent",
|
||||
"Natural Language :: English",
|
||||
"Natural Language :: French",
|
||||
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Home = "https://forge.cloud.silique.fr/stove/tiramisu"
|
||||
|
||||
[tool.commitizen]
|
||||
name = "cz_conventional_commits"
|
||||
tag_format = "$version"
|
||||
version_scheme = "pep440"
|
||||
version_provider = "pep621"
|
||||
#update_changelog_on_bump = true
|
||||
changelog_merge_prerelease = true
|
51
setup.py
|
@ -1,51 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
from setuptools import setup
|
||||
import os
|
||||
from tiramisu import __version__
|
||||
|
||||
|
||||
ORI_PACKAGE_NAME = 'tiramisu'
|
||||
PACKAGE_NAME = os.environ.get('PACKAGE_DST', ORI_PACKAGE_NAME)
|
||||
|
||||
if PACKAGE_NAME != ORI_PACKAGE_NAME:
|
||||
package_dir = {PACKAGE_NAME: ORI_PACKAGE_NAME}
|
||||
else:
|
||||
package_dir = None
|
||||
|
||||
setup(
|
||||
version=__version__,
|
||||
author="Tiramisu's team",
|
||||
author_email='gnunux@gnunux.info',
|
||||
name=PACKAGE_NAME,
|
||||
description='an options controller tool',
|
||||
url='https://framagit.org/tiramisu/tiramisu',
|
||||
license='GNU Library or Lesser General Public License (LGPL)',
|
||||
provides=['tiramisu_api'],
|
||||
classifiers=[
|
||||
"Programming Language :: Python :: 3",
|
||||
"License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
|
||||
"Operating System :: OS Independent",
|
||||
"Natural Language :: English",
|
||||
"Natural Language :: French",
|
||||
],
|
||||
long_description="""\
|
||||
An options controller tool
|
||||
-------------------------------------
|
||||
|
||||
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
|
||||
options to where they are actually used and even more annoying to add new
|
||||
options. To circumvent these problems the configuration control was
|
||||
introduced...
|
||||
|
||||
Tiramisu is an options handler and an options controller, wich aims at
|
||||
producing flexible and fast options access.
|
||||
|
||||
|
||||
This version requires Python 3.5 or later.
|
||||
""",
|
||||
include_package_data=True,
|
||||
package_dir=package_dir,
|
||||
packages=[PACKAGE_NAME],
|
||||
)
|
|
@ -42,3 +42,24 @@ def global_owner(config, config_type):
|
|||
@pytest.fixture(params=PARAMS)
|
||||
def config_type(request):
|
||||
return request.param
|
||||
|
||||
|
||||
def parse_od_get(dico):
|
||||
ret = {}
|
||||
for k, v in dico.items():
|
||||
if k.isoptiondescription():
|
||||
if k.isleadership():
|
||||
leader_path = k.leader().path()
|
||||
ret_leadership = []
|
||||
for variable, value in v.items():
|
||||
if variable.path() == leader_path:
|
||||
for val in value:
|
||||
ret_leadership.append({leader_path: val})
|
||||
else:
|
||||
ret_leadership[variable.index()][variable.path()] = value
|
||||
ret[leader_path] = ret_leadership
|
||||
else:
|
||||
ret.update(parse_od_get(v))
|
||||
else:
|
||||
ret[k.path()] = v
|
||||
return ret
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"options.unicode": null
|
||||
}
|
|
@ -32,6 +32,9 @@ def list_data(ext='.py'):
|
|||
for filename in filenames:
|
||||
# if filename.endswith(ext) and not filename.startswith('__'):
|
||||
if filename.endswith(ext) and not filename.startswith('__') and not 'not_equal' in filename and not 'callback' in filename and not filename == 'unicode2_copy.py' and not filename == 'unicode2_multi_copy.py':
|
||||
# if 'leadership' in filename:
|
||||
# print('FIXME')
|
||||
# continue
|
||||
ret.append(filename)
|
||||
return ret
|
||||
|
||||
|
@ -58,7 +61,7 @@ def load_config(filename,
|
|||
form.extend(mod.get_form(add_extra_od))
|
||||
config.property.read_write()
|
||||
if root is None:
|
||||
values = loads(dumps(config.option.dict(remotable=remote, clearable=clearable, form=form)))
|
||||
values = loads(dumps(config.dict(remotable=remote, clearable=clearable, form=form)))
|
||||
else:
|
||||
values = loads(dumps(config.option(root).dict(remotable=remote, clearable=clearable, form=form)))
|
||||
return values
|
||||
|
@ -380,11 +383,12 @@ def test_updates(filename_mod):
|
|||
if dico_ori is None:
|
||||
if clearable == 'minimum' and remote == 'minimum':
|
||||
with open(join(datadir, modulepath + '.dict'), 'w') as fh:
|
||||
dump(config.value.dict(), fh, indent=2)
|
||||
pouet
|
||||
dump(config.value.get(), fh, indent=2)
|
||||
else:
|
||||
assert config.value.dict() == dico_ori, "clearable {}, remote: {}, filename: {}".format(clearable, remote, filename_mod)
|
||||
assert config.value.get() == dico_ori, "clearable {}, remote: {}, filename: {}".format(clearable, remote, filename_mod)
|
||||
if root is None:
|
||||
suboption = config.option
|
||||
suboption = config
|
||||
else:
|
||||
suboption = config.option(root)
|
||||
if with_model:
|
||||
|
|
|
@ -54,12 +54,13 @@ def test_cache_importation():
|
|||
od1 = make_description()
|
||||
cfg = Config(od1)
|
||||
cfg.option('u2').value.set(1)
|
||||
values = cfg._config_bag.context._impl_values_cache
|
||||
export = cfg.value.exportation()
|
||||
assert cfg.value.dict() == {'u1': [], 'u2': 1, 'u3': []}
|
||||
compare(values.get_cached(), {'u2': {None: (1, None)}})
|
||||
cfg.option('u2').value.set(2)
|
||||
assert cfg.value.dict() == {'u1': [], 'u2': 2, 'u3': []}
|
||||
compare(values.get_cached(), {'u2': {None: (2, None)}})
|
||||
cfg.value.importation(export)
|
||||
assert cfg.value.dict() == {'u1': [], 'u2': 1, 'u3': []}
|
||||
compare(values.get_cached(), {})
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
|
@ -80,10 +81,10 @@ def test_cache_importation_property():
|
|||
def test_cache_importation_permissive():
|
||||
od1 = make_description()
|
||||
cfg = Config(od1)
|
||||
cfg.option('u2').permissive.set(frozenset(['prop']))
|
||||
cfg.option('u2').permissive.add('prop')
|
||||
export = cfg.permissive.exportation()
|
||||
assert cfg.option('u2').permissive.get() == {'prop'}
|
||||
cfg.option('u2').permissive.set(frozenset(['prop', 'prop2']))
|
||||
cfg.option('u2').permissive.add('prop2')
|
||||
assert cfg.option('u2').permissive.get() == {'prop', 'prop2'}
|
||||
cfg.permissive.importation(export)
|
||||
assert cfg.option('u2').permissive.get() == {'prop'}
|
||||
|
@ -265,7 +266,7 @@ def test_cache_leadership():
|
|||
#assert cache['ip_admin_eth0.netmask_admin_eth0'][None][0] == [None]
|
||||
#assert cache['ip_admin_eth0.netmask_admin_eth0'][0][0] is None
|
||||
cache = settings.get_cached()
|
||||
assert set(cache.keys()) == set([None, 'ip_admin_eth0', 'ip_admin_eth0.ip_admin_eth0', 'ip_admin_eth0.netmask_admin_eth0'])
|
||||
assert set(cache.keys()) == set(['ip_admin_eth0', 'ip_admin_eth0.ip_admin_eth0', 'ip_admin_eth0.netmask_admin_eth0'])
|
||||
assert set(cache['ip_admin_eth0'].keys()) == set([None])
|
||||
assert set(cache['ip_admin_eth0.ip_admin_eth0'].keys()) == set([None])
|
||||
assert set(cache['ip_admin_eth0.netmask_admin_eth0'].keys()) == {0}
|
||||
|
@ -283,7 +284,7 @@ def test_cache_leadership():
|
|||
#assert cache['ip_admin_eth0.netmask_admin_eth0'][0][0] is None
|
||||
#assert cache['ip_admin_eth0.netmask_admin_eth0'][1][0] is None
|
||||
cache = settings.get_cached()
|
||||
assert set(cache.keys()) == set([None, 'ip_admin_eth0', 'ip_admin_eth0.ip_admin_eth0', 'ip_admin_eth0.netmask_admin_eth0'])
|
||||
assert set(cache.keys()) == set(['ip_admin_eth0', 'ip_admin_eth0.ip_admin_eth0', 'ip_admin_eth0.netmask_admin_eth0'])
|
||||
assert set(cache['ip_admin_eth0'].keys()) == set([None])
|
||||
assert set(cache['ip_admin_eth0.ip_admin_eth0'].keys()) == set([None])
|
||||
assert set(cache['ip_admin_eth0.netmask_admin_eth0'].keys()) == set([0, 1])
|
||||
|
@ -309,7 +310,7 @@ def test_cache_callback():
|
|||
od1 = OptionDescription('rootconfig', '', [val1, val2, val3, val4, val5])
|
||||
cfg = Config(od1)
|
||||
cfg.property.read_write()
|
||||
cfg.value.dict()
|
||||
cfg.value.get()
|
||||
values = cfg._config_bag.context._impl_values_cache
|
||||
settings = cfg._config_bag.context.properties_cache
|
||||
compare(values.get_cached(), {'val1': {None: ('val', None)},
|
||||
|
@ -321,7 +322,7 @@ def test_cache_callback():
|
|||
compare(values.get_cached(), {'val3': {None: ('yes', None)},
|
||||
'val1': {None: ('new', None)},
|
||||
'val5': {None: (['yes'], None)}})
|
||||
cfg.value.dict()
|
||||
cfg.value.get()
|
||||
compare(values.get_cached(), {'val1': {None: ('new', None)},
|
||||
'val2': {None: ('new', None)},
|
||||
'val3': {None: ('yes', None)},
|
||||
|
@ -334,7 +335,7 @@ def test_cache_callback():
|
|||
'val1': {None: ('new', None)},
|
||||
'val3': {None: ('new2', None, True)},
|
||||
'val5': {None: (['yes'], None)}})
|
||||
cfg.value.dict()
|
||||
cfg.value.get()
|
||||
compare(values.get_cached(), {'val1': {None: ('new', None)},
|
||||
'val2': {None: ('new', None)},
|
||||
'val3': {None: ('new2', None)},
|
||||
|
@ -346,24 +347,12 @@ def test_cache_callback():
|
|||
'val3': {None: ('new2', None)},
|
||||
'val4': {None: ('new3', None, True)},
|
||||
'val5': {None: (['yes'], None)}})
|
||||
cfg.value.dict()
|
||||
cfg.value.get()
|
||||
compare(values.get_cached(), {'val1': {None: ('new', None)},
|
||||
'val2': {None: ('new', None)},
|
||||
'val3': {None: ('new2', None)},
|
||||
'val4': {None: ('new3', None)},
|
||||
'val5': {None: (['yes'], None)}})
|
||||
cfg.option('val5').value.set([undefined, 'new4'])
|
||||
compare(values.get_cached(), {'val1': {None: ('new', None)},
|
||||
'val2': {None: ('new', None)},
|
||||
'val3': {None: ('new2', None)},
|
||||
'val4': {None: ('new3', None)},
|
||||
'val5': {None: (['yes', 'new4'], None)}})
|
||||
cfg.value.dict()
|
||||
compare(values.get_cached(), {'val1': {None: ('new', None)},
|
||||
'val2': {None: ('new', None)},
|
||||
'val3': {None: ('new2', None)},
|
||||
'val4': {None: ('new3', None)},
|
||||
'val5': {None: (['yes', 'new4'], None)}})
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
|
@ -374,7 +363,7 @@ def test_cache_leader_and_followers():
|
|||
od1 = OptionDescription('rootconfig', '', [interface1])
|
||||
cfg = Config(od1)
|
||||
cfg.property.read_write()
|
||||
cfg.value.dict()
|
||||
cfg.value.get()
|
||||
global_props = ['cache', 'disabled', 'frozen', 'hidden', 'validator', 'warnings', 'force_store_value']
|
||||
val1_props = []
|
||||
val1_val1_props = ['empty', 'unique']
|
||||
|
@ -387,35 +376,31 @@ def test_cache_leader_and_followers():
|
|||
idx_val2 = None
|
||||
values = cfg._config_bag.context._impl_values_cache
|
||||
settings = cfg._config_bag.context.properties_cache
|
||||
compare(settings.get_cached(), {None: {None: (global_props, None)},
|
||||
'val1': {None: (val1_props, None)},
|
||||
compare(settings.get_cached(), {'val1': {None: (val1_props, None)},
|
||||
'val1.val1': {None: (val1_val1_props, None)},
|
||||
})
|
||||
# len is 0 so don't get any value
|
||||
compare(values.get_cached(), {'val1.val1': {None: ([], None)}})
|
||||
#
|
||||
cfg.option('val1.val1').value.set([undefined])
|
||||
cfg.option('val1.val1').value.set([None])
|
||||
val_val2_props = {idx_val2: (val1_val2_props, None), None: (set(), None)}
|
||||
compare(settings.get_cached(), {None: {None: (set(global_props), None)},
|
||||
'val1.val1': {None: (val1_val1_props, None)},
|
||||
})
|
||||
compare(settings.get_cached(), {'val1.val1': {None: ({'empty', 'unique'}, None, True)}})
|
||||
compare(values.get_cached(), {'val1.val1': {None: ([None], None, True)}})
|
||||
cfg.value.dict()
|
||||
cfg.value.get()
|
||||
#has value
|
||||
idx_val2 = 0
|
||||
val_val2 = None
|
||||
val_val2_props = {idx_val2: (val1_val2_props, None)}
|
||||
compare(settings.get_cached(), {None: {None: (global_props, None)},
|
||||
'val1': {None: (val1_props, None)},
|
||||
compare(settings.get_cached(), {'val1': {None: (val1_props, None)},
|
||||
'val1.val1': {None: (val1_val1_props, None)},
|
||||
'val1.val2': val_val2_props})
|
||||
compare(values.get_cached(), {'val1.val1': {None: ([None], None)},
|
||||
'val1.val2': {idx_val2: (val_val2, None)},
|
||||
})
|
||||
cfg.option('val1.val1').value.set([undefined, undefined])
|
||||
cfg.value.dict()
|
||||
cfg.option('val1.val1').value.set([None, None])
|
||||
cfg.value.get()
|
||||
cfg.option('val1.val2', 1).value.set('oui')
|
||||
compare(settings.get_cached(), {None: {None: (set(['cache', 'disabled', 'frozen', 'hidden', 'validator', 'warnings', 'force_store_value']), None)}})
|
||||
compare(settings.get_cached(), {})
|
||||
compare(values.get_cached(), {'val1.val2': {1: ('oui', None, True)}})
|
||||
val1_val2_props = {0: (frozenset([]), None), 1: (frozenset([]), None)}
|
||||
# assert not list_sessions()
|
||||
|
@ -428,7 +413,7 @@ def test_cache_leader_callback():
|
|||
od1 = OptionDescription('rootconfig', '', [interface1])
|
||||
cfg = Config(od1)
|
||||
cfg.property.read_write()
|
||||
cfg.value.dict()
|
||||
cfg.value.get()
|
||||
global_props = ['cache', 'disabled', 'frozen', 'hidden', 'validator', 'warnings', 'force_store_value']
|
||||
val1_props = []
|
||||
val1_val1_props = ['empty', 'unique']
|
||||
|
@ -439,18 +424,15 @@ def test_cache_leader_callback():
|
|||
val1_val2_props = frozenset(val1_val2_props)
|
||||
values = cfg._config_bag.context._impl_values_cache
|
||||
settings = cfg._config_bag.context.properties_cache
|
||||
compare(settings.get_cached(), {None: {None: (global_props, None)},
|
||||
'val1': {None: (val1_props, None)},
|
||||
compare(settings.get_cached(), {'val1': {None: (val1_props, None)},
|
||||
'val1.val1': {None: (val1_val1_props, None)},
|
||||
})
|
||||
compare(values.get_cached(), {'val1.val1': {None: ([], None)}})
|
||||
cfg.option('val1.val1').value.set([undefined])
|
||||
compare(settings.get_cached(), {None: {None: (set(global_props), None)},
|
||||
'val1.val1': {None: (val1_val1_props, None)},
|
||||
})
|
||||
cfg.option('val1.val1').value.set([None])
|
||||
compare(settings.get_cached(), {'val1.val1': {None: ({'unique', 'empty'}, None, True)}})
|
||||
|
||||
compare(values.get_cached(), {'val1.val1': {None: ([None], None, True)}})
|
||||
cfg.value.dict()
|
||||
cfg.value.get()
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
|
@ -469,38 +451,33 @@ def test_cache_requires():
|
|||
settings = cfg._config_bag.context.properties_cache
|
||||
assert values.get_cached() == {}
|
||||
assert cfg.option('ip_address_service').value.get() == None
|
||||
compare(settings.get_cached(), {None: {None: (set(['cache', 'disabled', 'frozen', 'hidden', 'validator', 'warnings', 'force_store_value']), None)},
|
||||
'activate_service': {None: (set([]), None)},
|
||||
compare(settings.get_cached(), {'activate_service': {None: (set([]), None)},
|
||||
'ip_address_service': {None: (set([]), None)}})
|
||||
|
||||
compare(values.get_cached(), {'ip_address_service': {None: (None, None)},
|
||||
'activate_service': {None: (True, None)}})
|
||||
cfg.value.dict()
|
||||
compare(settings.get_cached(), {None: {None: (set(['cache', 'disabled', 'frozen', 'hidden', 'validator', 'warnings', 'force_store_value']), None)},
|
||||
'activate_service': {None: (set([]), None)},
|
||||
cfg.value.get()
|
||||
compare(settings.get_cached(), {'activate_service': {None: (set([]), None)},
|
||||
'ip_address_service': {None: (set([]), None)}})
|
||||
|
||||
compare(values.get_cached(), {'ip_address_service': {None: (None, None)},
|
||||
'activate_service': {None: (True, None)}})
|
||||
cfg.option('ip_address_service').value.set('1.1.1.1')
|
||||
compare(settings.get_cached(), {None: {None: (set(['cache', 'disabled', 'frozen', 'hidden', 'validator', 'warnings', 'force_store_value']), None)},
|
||||
'activate_service': {None: (set([]), None)}})
|
||||
compare(settings.get_cached(), {'activate_service': {None: (set([]), None)}})
|
||||
|
||||
compare(values.get_cached(), {'activate_service': {None: (True, None)}, 'ip_address_service': {None: ('1.1.1.1', None, True)}})
|
||||
cfg.value.dict()
|
||||
compare(settings.get_cached(), {None: {None: (set(['cache', 'disabled', 'frozen', 'hidden', 'validator', 'warnings', 'force_store_value']), None)},
|
||||
'activate_service': {None: (set([]), None)},
|
||||
cfg.value.get()
|
||||
compare(settings.get_cached(), {'activate_service': {None: (set([]), None)},
|
||||
'ip_address_service': {None: (set([]), None)}})
|
||||
|
||||
compare(values.get_cached(), {'ip_address_service': {None: ('1.1.1.1', None)},
|
||||
'activate_service': {None: (True, None)}})
|
||||
cfg.option('activate_service').value.set(False)
|
||||
compare(settings.get_cached(), {None: {None: (set(['cache', 'disabled', 'frozen', 'hidden', 'validator', 'warnings', 'force_store_value']), None)}})
|
||||
compare(settings.get_cached(), {})
|
||||
|
||||
compare(values.get_cached(), {'activate_service': {None: (False, None)}})
|
||||
cfg.value.dict()
|
||||
compare(settings.get_cached(), {None: {None: (set(['cache', 'disabled', 'frozen', 'hidden', 'validator', 'warnings', 'force_store_value']), None)},
|
||||
'activate_service': {None: (set([]), None)},
|
||||
cfg.value.get()
|
||||
compare(settings.get_cached(), {'activate_service': {None: (set([]), None)},
|
||||
'ip_address_service': {None: (set(['disabled']), None)}})
|
||||
|
||||
compare(values.get_cached(), {'activate_service': {None: (False, None)}})
|
||||
|
@ -522,21 +499,18 @@ def test_cache_global_properties():
|
|||
settings = cfg._config_bag.context.properties_cache
|
||||
assert values.get_cached() == {}
|
||||
assert cfg.option('ip_address_service').value.get() == None
|
||||
compare(settings.get_cached(), {None: {None: (set(['cache', 'disabled', 'frozen', 'hidden', 'validator', 'warnings', 'force_store_value']), None)},
|
||||
'activate_service': {None: (set([]), None)},
|
||||
compare(settings.get_cached(), {'activate_service': {None: (set([]), None)},
|
||||
'ip_address_service': {None: (set([]), None)}})
|
||||
|
||||
compare(values.get_cached(), {'ip_address_service': {None: (None, None)},
|
||||
'activate_service': {None: (True, None)}})
|
||||
cfg.property.remove('disabled')
|
||||
assert cfg.option('ip_address_service').value.get() == None
|
||||
compare(settings.get_cached(), {None: {None: (set(['cache', 'frozen', 'hidden', 'validator', 'warnings', 'force_store_value']), None)},
|
||||
'activate_service': {None: (set([]), None)},
|
||||
compare(settings.get_cached(), {'activate_service': {None: (set([]), None)},
|
||||
'ip_address_service': {None: (set([]), None)}})
|
||||
cfg.property.add('test')
|
||||
assert cfg.option('ip_address_service').value.get() == None
|
||||
compare(settings.get_cached(), {None: {None: (set(['cache', 'frozen', 'hidden', 'validator', 'warnings', 'test', 'force_store_value']), None)},
|
||||
'activate_service': {None: (set([]), None)},
|
||||
compare(settings.get_cached(), {'activate_service': {None: (set([]), None)},
|
||||
'ip_address_service': {None: (set([]), None)}})
|
||||
# assert not list_sessions()
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
# coding: utf-8
|
||||
from py.test import raises
|
||||
|
||||
from .autopath import do_autopath
|
||||
do_autopath()
|
||||
from .config import config_type, get_config, value_list, global_owner
|
||||
from .config import config_type, get_config, value_list, global_owner, parse_od_get
|
||||
|
||||
from pytest import raises
|
||||
from tiramisu import ChoiceOption, StrOption, OptionDescription, Config, owners, Calculation, \
|
||||
undefined, Params, ParamValue, ParamOption
|
||||
from tiramisu.error import ConfigError
|
||||
|
@ -73,6 +72,30 @@ def test_choiceoption_function(config_type):
|
|||
assert cfg.option('choice').owner.isdefault()
|
||||
#
|
||||
assert value_list(cfg.option('choice').value.list()) == ('val1', 'val2')
|
||||
assert isinstance(cfg.option('choice').value.list(uncalculated=True), Calculation)
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_choiceoption_subfunction(config_type):
|
||||
choice = ChoiceOption('choice', '', values=(Calculation(return_val, Params(ParamValue('val1'))), Calculation(return_val, Params(ParamValue('val2')))))
|
||||
od1 = OptionDescription('od', '', [choice])
|
||||
cfg = Config(od1)
|
||||
cfg.property.read_write()
|
||||
cfg = get_config(cfg, config_type)
|
||||
owner = global_owner(cfg, config_type)
|
||||
assert cfg.option('choice').owner.isdefault()
|
||||
#
|
||||
cfg.option('choice').value.set('val1')
|
||||
assert cfg.option('choice').owner.get() == owner
|
||||
#
|
||||
cfg.option('choice').value.reset()
|
||||
assert cfg.option('choice').owner.isdefault()
|
||||
#
|
||||
with raises(ValueError):
|
||||
cfg.option('choice').value.set('no')
|
||||
assert cfg.option('choice').owner.isdefault()
|
||||
#
|
||||
assert value_list(cfg.option('choice').value.list()) == ('val1', 'val2')
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
|
@ -262,3 +285,13 @@ def test_choiceoption_calc_not_list():
|
|||
with raises(ConfigError):
|
||||
cfg.option('choice').value.set(['val1'])
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_choiceoption_calc_default_value():
|
||||
var1 = StrOption("var1", '', default="val1")
|
||||
var2 = StrOption("var2", '', default="val2")
|
||||
choice = ChoiceOption("choice", '', values=(Calculation(return_val, Params((ParamOption(var1)))), Calculation(return_val, Params((ParamOption(var2))))), default="val1")
|
||||
od2 = OptionDescription("rougail", '', children=[var1, var2, choice])
|
||||
od1 = OptionDescription("baseoption", "", children=[od2])
|
||||
cfg = Config(od1)
|
||||
assert parse_od_get(cfg.value.get()) == {'rougail.var1': 'val1', 'rougail.var2': 'val2', 'rougail.choice': 'val1'}
|
||||
|
|
|
@ -9,10 +9,14 @@ do_autopath()
|
|||
from .config import config_type, get_config, value_list, global_owner
|
||||
|
||||
import pytest
|
||||
from tiramisu import Config
|
||||
from tiramisu import Config, Calculation, Params, ParamSelfInformation, calc_value
|
||||
from tiramisu.i18n import _
|
||||
from tiramisu import Config, IntOption, FloatOption, ChoiceOption, \
|
||||
BoolOption, StrOption, SymLinkOption, OptionDescription, undefined
|
||||
BoolOption, StrOption, SymLinkOption, OptionDescription, undefined, \
|
||||
DomainnameOption, EmailOption, URLOption, RegexpOption, IPOption, \
|
||||
PortOption, NetworkOption, NetmaskOption, BroadcastOption, UsernameOption, \
|
||||
GroupnameOption, DateOption, FilenameOption, PasswordOption, MACOption, \
|
||||
PermissionsOption
|
||||
from tiramisu.error import ConflictError, ConfigError, PropertiesOptionError
|
||||
|
||||
|
||||
|
@ -26,8 +30,7 @@ def make_description():
|
|||
floatoption = FloatOption('float', 'Test float option', default=2.3)
|
||||
stroption = StrOption('str', 'Test string option', default="abc", properties=('mandatory', ))
|
||||
boolop = BoolOption('boolop', 'Test boolean option op', default=True, properties=('hidden',))
|
||||
wantref_option = BoolOption('wantref', 'Test requires', default=False)
|
||||
wantref_option.impl_set_information('info', 'default value')
|
||||
wantref_option = BoolOption('wantref', 'Test requires', default=False, informations={'info': 'default value'})
|
||||
wantframework_option = BoolOption('wantframework', 'Test requires',
|
||||
default=False)
|
||||
|
||||
|
@ -130,6 +133,29 @@ def test_not_valid_properties():
|
|||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_information_load():
|
||||
ChoiceOption('a', '', ('a', 'b'), informations={'info': 'value'})
|
||||
BoolOption('a', '', informations={'info': 'value'})
|
||||
IntOption('a', '', informations={'info': 'value'})
|
||||
FloatOption('a', '', informations={'info': 'value'})
|
||||
StrOption('a', '', informations={'info': 'value'})
|
||||
RegexpOption('a', '', informations={'info': 'value'})
|
||||
IPOption('a', '', informations={'info': 'value'})
|
||||
PortOption('a', '', informations={'info': 'value'})
|
||||
NetworkOption('a', '', informations={'info': 'value'})
|
||||
NetmaskOption('a', '', informations={'info': 'value'})
|
||||
BroadcastOption('a', '', informations={'info': 'value'})
|
||||
DomainnameOption('a', '', informations={'info': 'value'})
|
||||
EmailOption('a', '', informations={'info': 'value'})
|
||||
URLOption('a', '', informations={'info': 'value'})
|
||||
UsernameOption('a', '', informations={'info': 'value'})
|
||||
GroupnameOption('a', '', informations={'info': 'value'})
|
||||
DateOption('a', '', informations={'info': 'value'})
|
||||
FilenameOption('a', '', informations={'info': 'value'})
|
||||
PasswordOption('a', '', informations={'info': 'value'})
|
||||
MACOption('a', '', informations={'info': 'value'})
|
||||
PermissionsOption('a', '', informations={'info': 'value'})
|
||||
|
||||
def test_information_config():
|
||||
od1 = make_description()
|
||||
cfg = Config(od1)
|
||||
|
@ -143,11 +169,11 @@ def test_information_config():
|
|||
with pytest.raises(ValueError):
|
||||
cfg.information.get('noinfo')
|
||||
assert cfg.information.get('noinfo', 'default') == 'default'
|
||||
cfg.information.reset('info')
|
||||
cfg.information.remove('info')
|
||||
with pytest.raises(ValueError):
|
||||
cfg.information.get('info')
|
||||
cfg.information.remove('info')
|
||||
with pytest.raises(ValueError):
|
||||
cfg.information.reset('noinfo')
|
||||
cfg.information.remove('noinfo')
|
||||
assert list(cfg.information.list()) == ['doc']
|
||||
# assert not list_sessions()
|
||||
|
||||
|
@ -194,26 +220,23 @@ def test_information_option():
|
|||
with pytest.raises(ValueError):
|
||||
cfg.option('gc.name').information.get('noinfo')
|
||||
assert cfg.option('gc.name').information.get('noinfo', 'default') == 'default'
|
||||
cfg.option('gc.name').information.reset('info')
|
||||
cfg.option('gc.name').information.remove('info')
|
||||
with pytest.raises(ValueError):
|
||||
cfg.option('gc.name').information.get('info')
|
||||
with pytest.raises(ValueError):
|
||||
cfg.option('gc.name').information.reset('noinfo')
|
||||
cfg.option('gc.name').information.remove('noinfo')
|
||||
assert list(cfg.option('gc.name').information.list()) == ['doc']
|
||||
#
|
||||
assert cfg.option('wantref').information.get('info') == 'default value'
|
||||
cfg.option('wantref').information.set('info', 'default value')
|
||||
assert cfg.option('wantref').information.get('info') == 'default value'
|
||||
cfg.option('wantref').information.reset('info')
|
||||
cfg.option('wantref').information.remove('info')
|
||||
assert cfg.option('wantref').information.get('info') == 'default value'
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_information_option_2():
|
||||
i1 = IntOption('test1', '')
|
||||
i1.impl_set_information('info', 'value')
|
||||
# it's a dict
|
||||
assert set(i1.impl_list_information()) == {'info', 'doc'}
|
||||
i1 = IntOption('test1', '', informations={'info': 'value'})
|
||||
od1 = OptionDescription('test', '', [i1])
|
||||
cfg = Config(od1)
|
||||
# it's tuples
|
||||
|
@ -221,6 +244,17 @@ def test_information_option_2():
|
|||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_information_option_symlink():
|
||||
i1 = IntOption('test1', '', Calculation(calc_value, Params(ParamSelfInformation('info'))), informations={'info': 'value'})
|
||||
i2 = SymLinkOption('test2', i1)
|
||||
od1 = OptionDescription('test', '', [i2, i1])
|
||||
cfg = Config(od1)
|
||||
# it's tuples
|
||||
assert set(cfg.option('test1').information.list()) == {'info', 'doc'}
|
||||
assert set(cfg.option('test2').information.list()) == {'info', 'doc'}
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_information_optiondescription():
|
||||
od1 = make_description()
|
||||
cfg = Config(od1)
|
||||
|
@ -234,11 +268,11 @@ def test_information_optiondescription():
|
|||
with pytest.raises(ValueError):
|
||||
cfg.option('gc').information.get('noinfo')
|
||||
assert cfg.option('gc').information.get('noinfo', 'default') == 'default'
|
||||
cfg.option('gc').information.reset('info')
|
||||
cfg.option('gc').information.remove('info')
|
||||
with pytest.raises(ValueError):
|
||||
cfg.option('gc').information.get('info')
|
||||
with pytest.raises(ValueError):
|
||||
cfg.option('gc').information.reset('noinfo')
|
||||
cfg.option('gc').information.remove('noinfo')
|
||||
assert list(cfg.option('gc').information.list()) == ['doc']
|
||||
# assert not list_sessions()
|
||||
|
||||
|
@ -264,12 +298,12 @@ def test_get_modified_values():
|
|||
compare(cfg.value.exportation(), {'od.g5': {None: ['yes', 'user']}})
|
||||
cfg.option('od.g4').value.set(False)
|
||||
compare(cfg.value.exportation(), {'od.g5': {None: ['yes', 'user']}, 'od.g4': {None: [False, 'user']}})
|
||||
cfg.option('od.g4').value.set(undefined)
|
||||
cfg.option('od.g4').value.set(True)
|
||||
compare(cfg.value.exportation(), {'od.g5': {None: ['yes', 'user']}, 'od.g4': {None: [True, 'user']}})
|
||||
cfg.option('od.g4').value.reset()
|
||||
compare(cfg.value.exportation(), {'od.g5': {None: ['yes', 'user']}})
|
||||
assert cfg.option('od.g6').ismulti()
|
||||
cfg.option('od.g6').value.set([undefined])
|
||||
cfg.option('od.g6').value.set([None])
|
||||
compare(cfg.value.exportation(), {'od.g5': {None: ['yes', 'user']}, 'od.g6': {None: [[None], 'user']}})
|
||||
cfg.option('od.g6').value.set([])
|
||||
compare(cfg.value.exportation(), {'od.g5': {None: ['yes', 'user']}, 'od.g6': {None: [[], 'user']}})
|
||||
|
@ -329,10 +363,10 @@ def test_config_multi(config_type):
|
|||
cfg = get_config(cfg, config_type)
|
||||
assert cfg.option('test1').value.get() == []
|
||||
assert cfg.option('test2').value.get() == []
|
||||
cfg.option('test2').value.set([undefined])
|
||||
cfg.option('test2').value.set([1])
|
||||
assert cfg.option('test2').value.get() == [1]
|
||||
assert cfg.option('test3').value.get() == [2]
|
||||
cfg.option('test3').value.set([undefined, undefined])
|
||||
cfg.option('test3').value.set([2, 1])
|
||||
assert cfg.option('test3').value.get() == [2, 1]
|
||||
# assert not list_sessions()
|
||||
|
||||
|
@ -412,7 +446,7 @@ def test_config_od_name(config_type):
|
|||
cfg = get_config(cfg, config_type)
|
||||
assert cfg.option('val.i').name() == 'i'
|
||||
assert cfg.option('val.s').name() == 's'
|
||||
assert cfg.option('val.s').type() == _('integer')
|
||||
assert cfg.option('val.s').type() == 'integer'
|
||||
assert cfg.option('val').type() == 'optiondescription'
|
||||
# assert not list_sessions()
|
||||
|
||||
|
@ -424,7 +458,7 @@ def test_config_od_type(config_type):
|
|||
cfg = Config(o2)
|
||||
cfg = get_config(cfg, config_type)
|
||||
assert cfg.option('val').type() == 'optiondescription'
|
||||
assert cfg.option('val.i').type() == _('integer')
|
||||
assert cfg.option('val.i').type() == 'integer'
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ from pytest import raises
|
|||
|
||||
from .autopath import do_autopath
|
||||
do_autopath()
|
||||
from .config import config_type, get_config, value_list, global_owner
|
||||
from .config import config_type, get_config, value_list, global_owner, parse_od_get
|
||||
|
||||
from tiramisu import Config, IntOption, FloatOption, StrOption, ChoiceOption, \
|
||||
BoolOption, FilenameOption, SymLinkOption, IPOption, \
|
||||
|
@ -76,14 +76,12 @@ def test_make_dict(config_type):
|
|||
cfg.property.read_write()
|
||||
cfg.permissive.add('hidden')
|
||||
cfg = get_config(cfg, config_type)
|
||||
d = cfg.value.dict()
|
||||
assert d == {"s1.a": False, "int": 42}
|
||||
assert parse_od_get(cfg.value.get()) == {"s1.a": False, "int": 42}
|
||||
cfg.option('int').value.set(43)
|
||||
cfg.option('s1.a').value.set(True)
|
||||
d = cfg.value.dict()
|
||||
assert d == {"s1.a": True, "int": 43}
|
||||
assert parse_od_get(cfg.value.get()) == {"s1.a": True, "int": 43}
|
||||
if config_type == 'tiramisu':
|
||||
assert cfg.forcepermissive.value.dict() == {"s1.a": True, "s1.b": False, "int": 43}
|
||||
assert parse_od_get(cfg.forcepermissive.value.get()) == {"s1.a": True, "s1.b": False, "int": 43}
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
|
@ -98,22 +96,7 @@ def test_make_dict_sub(config_type):
|
|||
cfg.property.read_write()
|
||||
cfg.permissive.add('hidden')
|
||||
cfg = get_config(cfg, config_type)
|
||||
assert cfg.option('s1').value.dict() == {'s1.a': False}
|
||||
|
||||
|
||||
def test_make_dict_not_value(config_type):
|
||||
"serialization part of config to a dict"
|
||||
od1 = OptionDescription("opt", "", [
|
||||
OptionDescription("s1", "", [
|
||||
BoolOption("a", "", default=False),
|
||||
BoolOption("b", "", default=False, properties=('hidden',))]),
|
||||
IntOption("int", "", default=42)])
|
||||
cfg = Config(od1)
|
||||
cfg.property.read_write()
|
||||
cfg.permissive.add('hidden')
|
||||
cfg = get_config(cfg, config_type)
|
||||
with raises(ConfigError):
|
||||
cfg.option('s1.a').value.dict()
|
||||
assert parse_od_get(cfg.option('s1').value.get()) == {'s1.a': False}
|
||||
|
||||
|
||||
def test_make_dict_with_disabled(config_type):
|
||||
|
@ -128,10 +111,10 @@ def test_make_dict_with_disabled(config_type):
|
|||
cfg = Config(od1)
|
||||
cfg.property.read_only()
|
||||
cfg = get_config(cfg, config_type)
|
||||
assert cfg.value.dict() == {"s1.a": False, "int": 42}
|
||||
assert parse_od_get(cfg.value.get()) == {"s1.a": False, "int": 42}
|
||||
if config_type == 'tiramisu':
|
||||
assert cfg.forcepermissive.value.dict() == {"s1.a": False, "int": 42}
|
||||
assert cfg.unrestraint.value.dict() == {"int": 42, "s1.a": False, "s1.b": False, "s2.a": False, "s2.b": False}
|
||||
assert parse_od_get(cfg.forcepermissive.value.get()) == {"s1.a": False, "int": 42}
|
||||
assert parse_od_get(cfg.unrestraint.value.get()) == {"int": 42, "s1.a": False, "s1.b": False, "s2.a": False, "s2.b": False}
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
|
@ -147,8 +130,7 @@ def test_make_dict_with_disabled_in_callback(config_type):
|
|||
cfg = Config(od1)
|
||||
cfg.property.read_only()
|
||||
cfg = get_config(cfg, config_type)
|
||||
d = cfg.value.dict()
|
||||
assert d == {"s1.a": False, "int": 42}
|
||||
assert parse_od_get(cfg.value.get()) == {"s1.a": False, "int": 42}
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
|
@ -166,150 +148,150 @@ def test_make_dict_fullpath(config_type):
|
|||
cfg = Config(od1)
|
||||
cfg.property.read_only()
|
||||
cfg = get_config(cfg, config_type)
|
||||
assert cfg.value.dict() == {"opt.s1.a": False, "opt.int": 42, "introot": 42}
|
||||
assert cfg.option('opt').value.dict() == {"opt.s1.a": False, "opt.int": 42}
|
||||
assert parse_od_get(cfg.value.get()) == {"opt.s1.a": False, "opt.int": 42, "introot": 42}
|
||||
assert parse_od_get(cfg.option('opt').value.get()) == {"opt.s1.a": False, "opt.int": 42}
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_find_in_config():
|
||||
"finds option in config"
|
||||
od1 = make_description()
|
||||
cfg = Config(od1)
|
||||
cfg.property.read_only()
|
||||
cfg.permissive.add('hidden')
|
||||
ret = list(cfg.option.find('dummy'))
|
||||
assert len(ret) == 1
|
||||
_is_same_opt(ret[0].get(), cfg.option('gc.dummy').get())
|
||||
#def test_find_in_config():
|
||||
# "finds option in config"
|
||||
# od1 = make_description()
|
||||
# cfg = Config(od1)
|
||||
# cfg.property.read_only()
|
||||
# cfg.permissive.add('hidden')
|
||||
# ret = list(cfg.option.find('dummy'))
|
||||
# assert len(ret) == 1
|
||||
# _is_same_opt(ret[0].get(), cfg.option('gc.dummy').get())
|
||||
# #
|
||||
# ret_find = cfg.option.find('dummy', first=True)
|
||||
# ret = ret_find.get()
|
||||
# _is_same_opt(ret, cfg.option('gc.dummy').get())
|
||||
# #
|
||||
# ret = list(cfg.option.find('float'))
|
||||
# assert len(ret) == 2
|
||||
# _is_same_opt(ret[0].get(), cfg.option('gc.float').get())
|
||||
# _is_same_opt(ret[1].get(), cfg.option('float').get())
|
||||
# #
|
||||
# ret = cfg.option.find('bool', first=True)
|
||||
# _is_same_opt(ret.get(), cfg.option('gc.gc2.bool').get())
|
||||
# ret = cfg.option.find('bool', value=True, first=True)
|
||||
# _is_same_opt(ret.get(), cfg.option('bool').get())
|
||||
# ret = cfg.option.find('dummy', first=True)
|
||||
# _is_same_opt(ret.get(), cfg.option('gc.dummy').get())
|
||||
# ret = cfg.option.find('float', first=True)
|
||||
# _is_same_opt(ret.get(), cfg.option('gc.float').get())
|
||||
# ret = list(cfg.option.find('prop'))
|
||||
# assert len(ret) == 1
|
||||
# _is_same_opt(ret[0].get(), cfg.option('gc.prop').get())
|
||||
# #
|
||||
# ret = list(cfg.option.find('prop', value=None))
|
||||
# assert len(ret) == 1
|
||||
# ret = list(cfg.option.find('prop'))
|
||||
# assert len(ret) == 1
|
||||
# _is_same_opt(ret[0].get(), cfg.option('gc.prop').get())
|
||||
# #
|
||||
# cfg.property.read_write()
|
||||
# with raises(AttributeError):
|
||||
# ret = cfg.option.find('prop')
|
||||
# assert ret.get()
|
||||
# ret = list(cfg.unrestraint.option.find(name='prop'))
|
||||
# assert len(ret) == 2
|
||||
# _is_same_opt(ret[0].get(), cfg.unrestraint.option('gc.gc2.prop').get())
|
||||
# _is_same_opt(ret[1].get(), cfg.forcepermissive.option('gc.prop').get())
|
||||
# #
|
||||
# ret = list(cfg.forcepermissive.option.find('prop'))
|
||||
# assert len(ret) == 1
|
||||
# _is_same_opt(ret[0].get(), cfg.forcepermissive.option('gc.prop').get())
|
||||
# #
|
||||
# ret = cfg.forcepermissive.option.find('prop', first=True)
|
||||
# _is_same_opt(ret.get(), cfg.forcepermissive.option('gc.prop').get())
|
||||
# # combinaison of filters
|
||||
# ret = list(cfg.unrestraint.option.find('prop', type=BoolOption))
|
||||
# assert len(ret) == 1
|
||||
# _is_same_opt(ret[0].get(), cfg.unrestraint.option('gc.gc2.prop').get())
|
||||
# ret = cfg.unrestraint.option.find('prop', type=BoolOption, first=True)
|
||||
# _is_same_opt(ret.get(), cfg.unrestraint.option('gc.gc2.prop').get())
|
||||
# #
|
||||
# ret = list(cfg.option.find('dummy', value=False))
|
||||
# assert len(ret) == 1
|
||||
# _is_same_opt(ret[0].get(), cfg.option('gc.dummy').get())
|
||||
# #
|
||||
# ret = cfg.option.find('dummy', value=False, first=True)
|
||||
# _is_same_opt(ret.get(), cfg.option('gc.dummy').get())
|
||||
# #subcfgig
|
||||
# ret = list(cfg.option('gc').find('dummy'))
|
||||
# assert len(ret) == 1
|
||||
# _is_same_opt(ret[0].get(), cfg.option('gc.dummy').get())
|
||||
# #
|
||||
# ret = list(cfg.option('gc').find('float'))
|
||||
# assert len(ret) == 1
|
||||
# _is_same_opt(ret[0].get(), cfg.option('gc.float').get())
|
||||
# #
|
||||
# ret = list(cfg.option('gc.gc2').find('bool'))
|
||||
# assert len(ret) == 1
|
||||
# _is_same_opt(ret[0].get(), cfg.option('gc.gc2.bool').get())
|
||||
# ret = cfg.option('gc').find('bool', value=False, first=True)
|
||||
# _is_same_opt(ret.get(), cfg.option('gc.gc2.bool').get())
|
||||
# #
|
||||
# with raises(AttributeError):
|
||||
# ret = cfg.option('gc').find('bool', value=True, first=True)
|
||||
# assert ret.get()
|
||||
# #
|
||||
# with raises(AttributeError):
|
||||
# ret = cfg.option('gc').find('wantref')
|
||||
# ret.get()
|
||||
# #
|
||||
# ret = list(cfg.unrestraint.option('gc').find('prop'))
|
||||
# assert len(ret) == 2
|
||||
# _is_same_opt(ret[0].get(), cfg.unrestraint.option('gc.gc2.prop').get())
|
||||
# _is_same_opt(ret[1].get(), cfg.forcepermissive.option('gc.prop').get())
|
||||
# #
|
||||
# cfg.property.read_only()
|
||||
# ret = list(cfg.option('gc').find('prop'))
|
||||
# assert len(ret) == 1
|
||||
# _is_same_opt(ret[0].get(), cfg.option('gc.prop').get())
|
||||
# # not OptionDescription
|
||||
# with raises(AttributeError):
|
||||
# cfg.option.find('gc', first=True)
|
||||
# with raises(AttributeError):
|
||||
# cfg.option.find('gc2', first=True)
|
||||
## assert not list_sessions()
|
||||
#
|
||||
ret_find = cfg.option.find('dummy', first=True)
|
||||
ret = ret_find.get()
|
||||
_is_same_opt(ret, cfg.option('gc.dummy').get())
|
||||
#
|
||||
ret = list(cfg.option.find('float'))
|
||||
assert len(ret) == 2
|
||||
_is_same_opt(ret[0].get(), cfg.option('gc.float').get())
|
||||
_is_same_opt(ret[1].get(), cfg.option('float').get())
|
||||
#def test_find_multi():
|
||||
# b = BoolOption('bool', '', multi=True, properties=('notunique',))
|
||||
# od1 = OptionDescription('od', '', [b])
|
||||
# cfg = Config(od1)
|
||||
# #
|
||||
# with raises(AttributeError):
|
||||
# list(cfg.option.find('bool', value=True))
|
||||
# with raises(AttributeError):
|
||||
# list(cfg.option.find('bool', value=True, first=True))
|
||||
# cfg.option('bool').value.set([False])
|
||||
# with raises(AttributeError):
|
||||
# list(cfg.option.find('bool', value=True))
|
||||
# with raises(AttributeError):
|
||||
# list(cfg.option.find('bool', value=True, first=True))
|
||||
# cfg.option('bool').value.set([False, False])
|
||||
# with raises(AttributeError):
|
||||
# list(cfg.option.find('bool', value=True))
|
||||
# with raises(AttributeError):
|
||||
# list(cfg.option.find('bool', value=True, first=True))
|
||||
# cfg.option('bool').value.set([False, False, True])
|
||||
# ret = list(cfg.option.find('bool', value=True))
|
||||
# assert len(ret) == 1
|
||||
# _is_same_opt(ret[0].get(), b)
|
||||
# ret = cfg.option.find('bool', value=True, first=True)
|
||||
# _is_same_opt(ret.get(), b)
|
||||
## assert not list_sessions()
|
||||
#
|
||||
ret = cfg.option.find('bool', first=True)
|
||||
_is_same_opt(ret.get(), cfg.option('gc.gc2.bool').get())
|
||||
ret = cfg.option.find('bool', value=True, first=True)
|
||||
_is_same_opt(ret.get(), cfg.option('bool').get())
|
||||
ret = cfg.option.find('dummy', first=True)
|
||||
_is_same_opt(ret.get(), cfg.option('gc.dummy').get())
|
||||
ret = cfg.option.find('float', first=True)
|
||||
_is_same_opt(ret.get(), cfg.option('gc.float').get())
|
||||
ret = list(cfg.option.find('prop'))
|
||||
assert len(ret) == 1
|
||||
_is_same_opt(ret[0].get(), cfg.option('gc.prop').get())
|
||||
#
|
||||
ret = list(cfg.option.find('prop', value=None))
|
||||
assert len(ret) == 1
|
||||
ret = list(cfg.option.find('prop'))
|
||||
assert len(ret) == 1
|
||||
_is_same_opt(ret[0].get(), cfg.option('gc.prop').get())
|
||||
#
|
||||
cfg.property.read_write()
|
||||
with raises(AttributeError):
|
||||
ret = cfg.option.find('prop')
|
||||
assert ret.get()
|
||||
ret = list(cfg.unrestraint.option.find(name='prop'))
|
||||
assert len(ret) == 2
|
||||
_is_same_opt(ret[0].get(), cfg.unrestraint.option('gc.gc2.prop').get())
|
||||
_is_same_opt(ret[1].get(), cfg.forcepermissive.option('gc.prop').get())
|
||||
#
|
||||
ret = list(cfg.forcepermissive.option.find('prop'))
|
||||
assert len(ret) == 1
|
||||
_is_same_opt(ret[0].get(), cfg.forcepermissive.option('gc.prop').get())
|
||||
#
|
||||
ret = cfg.forcepermissive.option.find('prop', first=True)
|
||||
_is_same_opt(ret.get(), cfg.forcepermissive.option('gc.prop').get())
|
||||
# combinaison of filters
|
||||
ret = list(cfg.unrestraint.option.find('prop', type=BoolOption))
|
||||
assert len(ret) == 1
|
||||
_is_same_opt(ret[0].get(), cfg.unrestraint.option('gc.gc2.prop').get())
|
||||
ret = cfg.unrestraint.option.find('prop', type=BoolOption, first=True)
|
||||
_is_same_opt(ret.get(), cfg.unrestraint.option('gc.gc2.prop').get())
|
||||
#
|
||||
ret = list(cfg.option.find('dummy', value=False))
|
||||
assert len(ret) == 1
|
||||
_is_same_opt(ret[0].get(), cfg.option('gc.dummy').get())
|
||||
#
|
||||
ret = cfg.option.find('dummy', value=False, first=True)
|
||||
_is_same_opt(ret.get(), cfg.option('gc.dummy').get())
|
||||
#subcfgig
|
||||
ret = list(cfg.option('gc').find('dummy'))
|
||||
assert len(ret) == 1
|
||||
_is_same_opt(ret[0].get(), cfg.option('gc.dummy').get())
|
||||
#
|
||||
ret = list(cfg.option('gc').find('float'))
|
||||
assert len(ret) == 1
|
||||
_is_same_opt(ret[0].get(), cfg.option('gc.float').get())
|
||||
#
|
||||
ret = list(cfg.option('gc.gc2').find('bool'))
|
||||
assert len(ret) == 1
|
||||
_is_same_opt(ret[0].get(), cfg.option('gc.gc2.bool').get())
|
||||
ret = cfg.option('gc').find('bool', value=False, first=True)
|
||||
_is_same_opt(ret.get(), cfg.option('gc.gc2.bool').get())
|
||||
#
|
||||
with raises(AttributeError):
|
||||
ret = cfg.option('gc').find('bool', value=True, first=True)
|
||||
assert ret.get()
|
||||
#
|
||||
with raises(AttributeError):
|
||||
ret = cfg.option('gc').find('wantref')
|
||||
ret.get()
|
||||
#
|
||||
ret = list(cfg.unrestraint.option('gc').find('prop'))
|
||||
assert len(ret) == 2
|
||||
_is_same_opt(ret[0].get(), cfg.unrestraint.option('gc.gc2.prop').get())
|
||||
_is_same_opt(ret[1].get(), cfg.forcepermissive.option('gc.prop').get())
|
||||
#
|
||||
cfg.property.read_only()
|
||||
ret = list(cfg.option('gc').find('prop'))
|
||||
assert len(ret) == 1
|
||||
_is_same_opt(ret[0].get(), cfg.option('gc.prop').get())
|
||||
# not OptionDescription
|
||||
with raises(AttributeError):
|
||||
cfg.option.find('gc', first=True)
|
||||
with raises(AttributeError):
|
||||
cfg.option.find('gc2', first=True)
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_find_multi():
|
||||
b = BoolOption('bool', '', multi=True, properties=('notunique',))
|
||||
od1 = OptionDescription('od', '', [b])
|
||||
cfg = Config(od1)
|
||||
#
|
||||
with raises(AttributeError):
|
||||
list(cfg.option.find('bool', value=True))
|
||||
with raises(AttributeError):
|
||||
list(cfg.option.find('bool', value=True, first=True))
|
||||
cfg.option('bool').value.set([False])
|
||||
with raises(AttributeError):
|
||||
list(cfg.option.find('bool', value=True))
|
||||
with raises(AttributeError):
|
||||
list(cfg.option.find('bool', value=True, first=True))
|
||||
cfg.option('bool').value.set([False, False])
|
||||
with raises(AttributeError):
|
||||
list(cfg.option.find('bool', value=True))
|
||||
with raises(AttributeError):
|
||||
list(cfg.option.find('bool', value=True, first=True))
|
||||
cfg.option('bool').value.set([False, False, True])
|
||||
ret = list(cfg.option.find('bool', value=True))
|
||||
assert len(ret) == 1
|
||||
_is_same_opt(ret[0].get(), b)
|
||||
ret = cfg.option.find('bool', value=True, first=True)
|
||||
_is_same_opt(ret.get(), b)
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_does_not_find_in_config():
|
||||
od1 = make_description()
|
||||
cfg = Config(od1)
|
||||
with raises(AttributeError):
|
||||
list(cfg.option.find('IDontExist'))
|
||||
# assert not list_sessions()
|
||||
#def test_does_not_find_in_config():
|
||||
# od1 = make_description()
|
||||
# cfg = Config(od1)
|
||||
# with raises(AttributeError):
|
||||
# list(cfg.option.find('IDontExist'))
|
||||
## assert not list_sessions()
|
||||
|
||||
|
||||
def test_filename(config_type):
|
||||
|
@ -430,7 +412,6 @@ def test_help():
|
|||
cfg = Config(od2)
|
||||
cfg.help(_display=False)
|
||||
cfg.config.help(_display=False)
|
||||
cfg.option.help(_display=False)
|
||||
cfg.option('o').help(_display=False)
|
||||
cfg.option('o.s').help(_display=False)
|
||||
# assert not list_sessions()
|
||||
|
@ -448,7 +429,7 @@ def test_config_reset():
|
|||
#
|
||||
cfg.option('gc.gc2.bool').value.set(True)
|
||||
cfg.option('boolop').property.add('test')
|
||||
cfg.option('float').permissive.set(frozenset(['test']))
|
||||
cfg.option('float').permissive.add('test')
|
||||
cfg.option('wantref').information.set('info', 'info')
|
||||
assert cfg.option('gc.gc2.bool').value.get()
|
||||
assert cfg.option('boolop').property.get()
|
||||
|
|
|
@ -66,7 +66,7 @@ def test_deref_optiondescription():
|
|||
def test_deref_option_cache():
|
||||
b = BoolOption('b', '')
|
||||
o = OptionDescription('od', '', [b])
|
||||
o._build_cache()
|
||||
o._build_cache(None)
|
||||
w = weakref.ref(b)
|
||||
del(b)
|
||||
assert w() is not None
|
||||
|
@ -77,7 +77,7 @@ def test_deref_option_cache():
|
|||
def test_deref_optiondescription_cache():
|
||||
b = BoolOption('b', '')
|
||||
o = OptionDescription('od', '', [b])
|
||||
o._build_cache()
|
||||
o._build_cache(None)
|
||||
w = weakref.ref(o)
|
||||
del(b)
|
||||
assert w() is not None
|
||||
|
@ -201,7 +201,7 @@ def test_deref_symlink():
|
|||
def test_deref_dyn():
|
||||
a = StrOption('a', '', ['val1', 'val2'], multi=True)
|
||||
b = StrOption('b', '')
|
||||
dod = DynOptionDescription('dod', '', [b], suffixes=Calculation(funcname, Params((ParamOption(a),))))
|
||||
dod = DynOptionDescription('dod', '', [b], identifiers=Calculation(funcname, Params((ParamOption(a),))))
|
||||
o = OptionDescription('od', '', [dod, a])
|
||||
cfg = Config(o)
|
||||
w = weakref.ref(a)
|
||||
|
|
|
@ -80,15 +80,15 @@ def test_copy_force_store_value():
|
|||
assert conf.value.exportation() == {'creole.general.wantref': {None: [True, 'user']}}
|
||||
assert conf2.value.exportation() == {'creole.general.wantref': {None: [False, 'forced']}}
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_copy_force_store_value_metaconfig():
|
||||
od1 = make_description()
|
||||
meta = MetaConfig([], optiondescription=od1)
|
||||
conf = meta.config.new()
|
||||
assert meta.property.get() == conf.property.get()
|
||||
assert meta.permissive.get() == conf.permissive.get()
|
||||
conf.property.read_write()
|
||||
assert conf.value.exportation() == {'creole.general.wantref': {None: [False, 'forced']}}
|
||||
assert meta.value.exportation() == {}
|
||||
# assert not list_sessions()
|
||||
#
|
||||
#
|
||||
#def test_copy_force_store_value_metaconfig():
|
||||
# od1 = make_description()
|
||||
# meta = MetaConfig([], optiondescription=od1)
|
||||
# conf = meta.config.new()
|
||||
# assert meta.property.get() == conf.property.get()
|
||||
# assert meta.permissive.get() == conf.permissive.get()
|
||||
# conf.property.read_write()
|
||||
# assert conf.value.exportation() == {'creole.general.wantref': {None: [False, 'forced']}}
|
||||
# assert meta.value.exportation() == {}
|
||||
## assert not list_sessions()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# coding: utf-8
|
||||
#test_force_store_value coding: utf-8
|
||||
"frozen and hidden values"
|
||||
from .autopath import do_autopath
|
||||
do_autopath()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# coding: utf-8
|
||||
from .autopath import do_autopath
|
||||
do_autopath()
|
||||
from .config import config_type, get_config, value_list, global_owner
|
||||
from .config import config_type, get_config, value_list, global_owner, parse_od_get
|
||||
import pytest
|
||||
|
||||
from tiramisu.setting import groups, owners
|
||||
|
@ -59,10 +59,10 @@ def test_base_config(config_type):
|
|||
cfg = get_config(cfg, config_type)
|
||||
assert cfg.option('creole.general.activer_proxy_client').value.get() is False
|
||||
assert cfg.option('creole.general.nom_machine').value.get() == "eoleng"
|
||||
if config_type != 'tiramisu-api':
|
||||
ret = cfg.option.find('nom_machine', first=True)
|
||||
assert ret.value.get() == "eoleng"
|
||||
assert cfg.option('creole').value.dict() == {'creole.general.numero_etab': None, 'creole.general.nom_machine': 'eoleng', 'creole.general.nombre_interfaces': 1, 'creole.general.activer_proxy_client': False, 'creole.general.mode_conteneur_actif': False, 'creole.general.serveur_ntp': [], 'creole.general.time_zone': 'Paris', 'creole.interface1.ip_admin_eth0.ip_admin_eth0': None, 'creole.interface1.ip_admin_eth0.netmask_admin_eth0': None}
|
||||
# if config_type != 'tiramisu-api':
|
||||
# ret = cfg.option.find('nom_machine', first=True)
|
||||
# assert ret.value.get() == "eoleng"
|
||||
assert parse_od_get(cfg.option('creole').value.get()) == {'creole.general.numero_etab': None, 'creole.general.nom_machine': 'eoleng', 'creole.general.nombre_interfaces': 1, 'creole.general.activer_proxy_client': False, 'creole.general.mode_conteneur_actif': False, 'creole.general.serveur_ntp': [], 'creole.general.time_zone': 'Paris', 'creole.interface1.ip_admin_eth0.ip_admin_eth0': None, 'creole.interface1.ip_admin_eth0.netmask_admin_eth0': None}
|
||||
if config_type == 'tiramisu-api':
|
||||
cfg.send()
|
||||
# assert not list_sessions()
|
||||
|
@ -77,49 +77,40 @@ def test_get_group_type():
|
|||
assert grp.group_type() == 'family'
|
||||
assert isinstance(grp.group_type(), groups.GroupType)
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_iter_on_groups():
|
||||
od1 = make_description()
|
||||
cfg = Config(od1)
|
||||
cfg.property.read_write()
|
||||
result = cfg.option('creole').list('optiondescription',
|
||||
group_type=groups.family,
|
||||
)
|
||||
group_names = [res.name() for res in result]
|
||||
assert group_names == ['general', 'interface1']
|
||||
for i in cfg.option('creole').list('optiondescription',
|
||||
group_type=groups.family,
|
||||
):
|
||||
#test StopIteration
|
||||
break
|
||||
result = cfg.option('creole').list('option',
|
||||
group_type=groups.family,
|
||||
)
|
||||
assert list(result) == []
|
||||
result = cfg.option('creole.general').list('optiondescription',
|
||||
group_type=groups.family,
|
||||
)
|
||||
assert list(result) == []
|
||||
# assert not list_sessions()
|
||||
#
|
||||
#
|
||||
#def test_iter_on_groups():
|
||||
# od1 = make_description()
|
||||
# cfg = Config(od1)
|
||||
# cfg.property.read_write()
|
||||
# result = cfg.option('creole').list('optiondescription',
|
||||
# group_type=groups.family,
|
||||
# )
|
||||
# group_names = [res.name() for res in result]
|
||||
# assert group_names == ['general', 'interface1']
|
||||
# for i in cfg.option('creole').list('optiondescription',
|
||||
# group_type=groups.family,
|
||||
# ):
|
||||
# #test StopIteration
|
||||
# break
|
||||
# result = cfg.option('creole').list('option',
|
||||
# group_type=groups.family,
|
||||
# )
|
||||
# assert list(result) == []
|
||||
# result = cfg.option('creole.general').list('optiondescription',
|
||||
# group_type=groups.family,
|
||||
# )
|
||||
# assert list(result) == []
|
||||
## assert not list_sessions()
|
||||
|
||||
def test_list_recursive():
|
||||
od1 = make_description()
|
||||
cfg = Config(od1)
|
||||
cfg.property.read_write()
|
||||
result = cfg.option('creole').list('all')
|
||||
result = cfg.option('creole').list()
|
||||
group_names = [res.name() for res in result]
|
||||
assert group_names == ['general', 'interface1']
|
||||
#
|
||||
result = cfg.option.list(recursive=True)
|
||||
group_names = [res.name() for res in result]
|
||||
assert group_names == ['numero_etab', 'nom_machine', 'nombre_interfaces',
|
||||
'activer_proxy_client', 'mode_conteneur_actif',
|
||||
'serveur_ntp', 'time_zone', 'ip_admin_eth0',
|
||||
'netmask_admin_eth0']
|
||||
result = list(cfg.option.list(recursive=True, type='optiondescription'))
|
||||
group_names = [res.name() for res in result]
|
||||
assert group_names == ['creole', 'general', 'interface1', 'ip_admin_eth0']
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
|
@ -147,8 +138,7 @@ def test_iter_group_on_groups_force_permissive():
|
|||
cfg = Config(od1)
|
||||
cfg.property.read_write()
|
||||
cfg.permissive.add('hidden')
|
||||
result = cfg.forcepermissive.option('creole').list(type='optiondescription',
|
||||
group_type=groups.family)
|
||||
result = cfg.forcepermissive.option('creole').list()
|
||||
group_names = [res.name() for res in result]
|
||||
assert group_names == ['general', 'interface1', 'new']
|
||||
# assert not list_sessions()
|
||||
|
@ -159,8 +149,7 @@ def test_iter_on_groups_props():
|
|||
cfg = Config(od1)
|
||||
cfg.property.read_write()
|
||||
cfg.option('creole.interface1').property.add('disabled')
|
||||
result = cfg.option('creole').list(type='optiondescription',
|
||||
group_type=groups.family)
|
||||
result = cfg.option('creole').list()
|
||||
group_names = [res.name() for res in result]
|
||||
assert group_names == ['general']
|
||||
# assert not list_sessions()
|
||||
|
@ -170,20 +159,11 @@ def test_iter_on_empty_group():
|
|||
od1 = OptionDescription("name", "descr", [])
|
||||
cfg = Config(od1)
|
||||
cfg.property.read_write()
|
||||
result = list(cfg.option.list(type='optiondescription'))
|
||||
result = list(cfg.list())
|
||||
assert result == []
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_iter_not_group():
|
||||
od1 = OptionDescription("name", "descr", [])
|
||||
cfg = Config(od1)
|
||||
cfg.property.read_write()
|
||||
with pytest.raises(AssertionError):
|
||||
print(list(cfg.option.list(type='optiondescription', group_type='family')))
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_groups_with_leader():
|
||||
ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
|
||||
netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True)
|
||||
|
@ -214,32 +194,33 @@ def test_groups_is_leader(config_type):
|
|||
assert not cfg.option('leadership.netmask_admin_eth0').isleader()
|
||||
assert cfg.option('leadership.netmask_admin_eth0').isfollower()
|
||||
assert cfg.option('leadership.netmask_admin_eth0').path() == 'leadership.netmask_admin_eth0'
|
||||
assert cfg.option('leadership.netmask_admin_eth0').defaultmulti() == 'value'
|
||||
assert cfg.option('leadership.netmask_admin_eth0').value.defaultmulti() == 'value'
|
||||
if config_type == 'tiramisu-api':
|
||||
cfg.send()
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_leader_list(config_type):
|
||||
ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
|
||||
ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", ['val1'], multi=True)
|
||||
netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True, default_multi='value')
|
||||
interface1 = Leadership('leadership', '', [ip_admin_eth0, netmask_admin_eth0])
|
||||
od1 = OptionDescription('od', '', [interface1])
|
||||
cfg = Config(od1)
|
||||
cfg = get_config(cfg, config_type)
|
||||
ret = cfg.option.list('all')
|
||||
ret = cfg.list()
|
||||
assert len(ret) == 1
|
||||
assert ret[0].name() == 'leadership'
|
||||
#
|
||||
ret = cfg.option('leadership').list('all')
|
||||
ret = cfg.option('leadership').list()
|
||||
assert len(ret) == 2
|
||||
assert ret[0].name() == 'ip_admin_eth0'
|
||||
assert ret[1].name() == 'netmask_admin_eth0'
|
||||
assert ret[1].index() == 0
|
||||
#
|
||||
cfg.option('leadership.ip_admin_eth0').value.set(['a', 'b'])
|
||||
cfg.option('leadership.netmask_admin_eth0', 0).value.set('c')
|
||||
cfg.option('leadership.netmask_admin_eth0', 1).value.set('d')
|
||||
ret = cfg.option('leadership').list('all')
|
||||
ret = cfg.option('leadership').list()
|
||||
assert ret[0].name() == 'ip_admin_eth0'
|
||||
assert ret[1].name() == 'netmask_admin_eth0'
|
||||
# assert ret[1].option.index() == 0
|
||||
|
@ -303,7 +284,7 @@ def test_groups_with_leader_make_dict(config_type):
|
|||
od1 = OptionDescription('root', '', [interface1])
|
||||
cfg = Config(od1)
|
||||
cfg = get_config(cfg, config_type)
|
||||
assert cfg.value.dict() == {'ip_admin_eth0.ip_admin_eth0': []}
|
||||
assert parse_od_get(cfg.value.get()) == {'ip_admin_eth0.ip_admin_eth0': []}
|
||||
if config_type != 'tiramisu-api':
|
||||
# FIXME useful? already in leadership
|
||||
assert cfg.option('ip_admin_eth0.ip_admin_eth0').value.len() == 0
|
||||
|
@ -313,7 +294,7 @@ def test_groups_with_leader_make_dict(config_type):
|
|||
# FIXME
|
||||
assert cfg.option('ip_admin_eth0.ip_admin_eth0').value.len() == 2
|
||||
assert cfg.option('ip_admin_eth0.netmask_admin_eth0').value.len() == 2
|
||||
assert cfg.value.dict() == {'ip_admin_eth0.ip_admin_eth0': [{'ip_admin_eth0.ip_admin_eth0': 'ip1', 'ip_admin_eth0.netmask_admin_eth0': None}, {'ip_admin_eth0.ip_admin_eth0': 'ip2', 'ip_admin_eth0.netmask_admin_eth0': None}]}
|
||||
assert parse_od_get(cfg.value.get()) == {'ip_admin_eth0.ip_admin_eth0': [{'ip_admin_eth0.ip_admin_eth0': 'ip1', 'ip_admin_eth0.netmask_admin_eth0': None}, {'ip_admin_eth0.ip_admin_eth0': 'ip2', 'ip_admin_eth0.netmask_admin_eth0': None}]}
|
||||
if config_type == 'tiramisu-api':
|
||||
cfg.send()
|
||||
# assert not list_sessions()
|
||||
|
@ -336,7 +317,7 @@ def test_groups_with_leader_make_dict2(config_type):
|
|||
od1 = OptionDescription('root', '', [interface1])
|
||||
cfg = Config(od1)
|
||||
cfg = get_config(cfg, config_type)
|
||||
assert cfg.value.dict() == {'other.ip_admin_eth0': []}
|
||||
assert parse_od_get(cfg.value.get()) == {'other.ip_admin_eth0': []}
|
||||
if config_type != 'tiramisu-api':
|
||||
# FIXME useful? already in leadership
|
||||
assert cfg.option('other.ip_admin_eth0').value.len() == 0
|
||||
|
@ -346,7 +327,7 @@ def test_groups_with_leader_make_dict2(config_type):
|
|||
# FIXME
|
||||
assert cfg.option('other.ip_admin_eth0').value.len() == 2
|
||||
assert cfg.option('other.netmask_admin_eth0').value.len() == 2
|
||||
assert cfg.value.dict() == {'other.ip_admin_eth0': [{'other.ip_admin_eth0': 'ip1', 'other.netmask_admin_eth0': None}, {'other.ip_admin_eth0': 'ip2', 'other.netmask_admin_eth0': None}]}
|
||||
assert parse_od_get(cfg.value.get()) == {'other.ip_admin_eth0': [{'other.ip_admin_eth0': 'ip1', 'other.netmask_admin_eth0': None}, {'other.ip_admin_eth0': 'ip2', 'other.netmask_admin_eth0': None}]}
|
||||
if config_type == 'tiramisu-api':
|
||||
cfg.send()
|
||||
# assert not list_sessions()
|
||||
|
@ -411,7 +392,7 @@ def test_groups_with_leader_hidden_in_config():
|
|||
cfg.option('ip_admin_eth0.ip_admin_eth0').value.get()
|
||||
with pytest.raises(PropertiesOptionError):
|
||||
cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get()
|
||||
assert cfg.value.dict() == {}
|
||||
assert parse_od_get(cfg.value.get()) == {}
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
|
@ -426,16 +407,13 @@ def test_groups_with_leader_hidden_in_config2():
|
|||
assert cfg.forcepermissive.option('ip_admin_eth0.ip_admin_eth0').value.get() == []
|
||||
cfg.forcepermissive.option('ip_admin_eth0.ip_admin_eth0').value.set(['192.168.1.1'])
|
||||
assert cfg.forcepermissive.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get() is None
|
||||
with pytest.raises(PropertiesOptionError):
|
||||
cfg.option('ip_admin_eth0.ip_admin_eth0').value.get()
|
||||
assert cfg.option('ip_admin_eth0.ip_admin_eth0').value.get() == ['192.168.1.1']
|
||||
with pytest.raises(PropertiesOptionError):
|
||||
cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get()
|
||||
assert cfg.value.dict() == {'ip_admin_eth0.ip_admin_eth0': ['192.168.1.1']}
|
||||
assert cfg.value.dict(leader_to_list=True) == {'ip_admin_eth0.ip_admin_eth0': [{'ip_admin_eth0.ip_admin_eth0': '192.168.1.1'}]}
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_groups_with_leader_hidden_in_config2():
|
||||
def test_groups_with_leader_hidden_in_config3():
|
||||
ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
|
||||
netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True, properties=('hidden',))
|
||||
interface1 = Leadership('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
|
||||
|
@ -1064,7 +1042,7 @@ def test_follower_force_store_value_reset():
|
|||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_follower_properties():
|
||||
#def test_follower_properties():
|
||||
ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
|
||||
netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True, properties=('aproperty',))
|
||||
interface1 = Leadership('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
# coding: utf-8
|
||||
from .autopath import do_autopath
|
||||
do_autopath()
|
||||
from .config import parse_od_get
|
||||
# FIXME from .config import config_type, get_config
|
||||
|
||||
import pytest
|
||||
from tiramisu import Config
|
||||
from tiramisu import IntOption, StrOption, OptionDescription, DynOptionDescription, \
|
||||
SymLinkOption, Leadership, undefined, Calculation, Params, \
|
||||
from tiramisu import IntOption, StrOption, OptionDescription, DynOptionDescription, PasswordOption, UsernameOption, \
|
||||
SymLinkOption, Leadership, Calculation, Params, \
|
||||
ParamOption, ParamValue, ParamIndex, calc_value
|
||||
from tiramisu.error import PropertiesOptionError, ConfigError
|
||||
from tiramisu.setting import groups
|
||||
|
@ -14,6 +15,8 @@ from tiramisu.setting import groups
|
|||
|
||||
#def teardown_function(function):
|
||||
# assert list_sessions() == [], 'session list is not empty when leaving "{}"'.format(function.__name__)
|
||||
def is_mandatory(variable):
|
||||
return True
|
||||
|
||||
|
||||
def make_description():
|
||||
|
@ -48,18 +51,6 @@ def make_description2():
|
|||
return descr
|
||||
|
||||
|
||||
def make_description_sym():
|
||||
stroption = StrOption('str', 'Test string option', default="abc",
|
||||
properties=('mandatory', ))
|
||||
stroption1 = StrOption('str1', 'Test string option',
|
||||
properties=('mandatory', ))
|
||||
stroption2 = SymLinkOption('unicode2', stroption1)
|
||||
stroption3 = StrOption('str3', 'Test string option', multi=True,
|
||||
properties=('mandatory', ))
|
||||
descr = OptionDescription('tiram', '', [stroption, stroption1, stroption2, stroption3])
|
||||
return descr
|
||||
|
||||
|
||||
def make_description3():
|
||||
stroption = StrOption('str', 'Test string option', default="abc",
|
||||
properties=('mandatory', ))
|
||||
|
@ -97,7 +88,7 @@ def test_mandatory_ro_dict():
|
|||
cfg.property.read_only()
|
||||
prop = []
|
||||
try:
|
||||
cfg.value.dict()
|
||||
cfg.value.get()
|
||||
except PropertiesOptionError as err:
|
||||
prop = err.proptype
|
||||
assert 'mandatory' in prop
|
||||
|
@ -106,14 +97,14 @@ def test_mandatory_ro_dict():
|
|||
cfg.option('unicode2').value.set('yes')
|
||||
cfg.property.read_only()
|
||||
try:
|
||||
cfg.value.dict()
|
||||
cfg.value.get()
|
||||
except PropertiesOptionError as err:
|
||||
prop = err.proptype
|
||||
assert 'mandatory' in prop
|
||||
cfg.property.read_write()
|
||||
cfg.option('str3').value.set(['yes'])
|
||||
cfg.property.read_only()
|
||||
assert cfg.value.dict() == {'str': 'abc', 'str1': 'yes', 'str3': ['yes'], 'unicode2': 'yes'}
|
||||
assert parse_od_get(cfg.value.get()) == {'str': 'abc', 'str1': 'yes', 'str3': ['yes'], 'unicode2': 'yes'}
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
|
@ -412,7 +403,7 @@ def test_mandatory_leader():
|
|||
with pytest.raises(PropertiesOptionError):
|
||||
cfg.option('ip_admin_eth0.ip_admin_eth0').value.get()
|
||||
with pytest.raises(PropertiesOptionError):
|
||||
cfg.value.dict()
|
||||
cfg.value.get()
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
|
@ -429,7 +420,7 @@ def test_mandatory_leader_sub():
|
|||
with pytest.raises(PropertiesOptionError):
|
||||
cfg.option('o.ip_admin_eth0.ip_admin_eth0').value.get()
|
||||
with pytest.raises(PropertiesOptionError):
|
||||
cfg.value.dict()
|
||||
cfg.value.get()
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
|
@ -455,7 +446,7 @@ def test_mandatory_leader_empty():
|
|||
cfg.property.read_write()
|
||||
assert cfg.option('ip_admin_eth0.ip_admin_eth0').value.get() == []
|
||||
#
|
||||
cfg.option('ip_admin_eth0.ip_admin_eth0').value.set([undefined])
|
||||
cfg.option('ip_admin_eth0.ip_admin_eth0').value.set([None])
|
||||
assert cfg.option('ip_admin_eth0.ip_admin_eth0').value.get() == [None]
|
||||
assert cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get() == None
|
||||
cfg.property.read_only()
|
||||
|
@ -501,7 +492,7 @@ def test_mandatory_warnings_leader_empty():
|
|||
od1 = OptionDescription('o', '', [interface1])
|
||||
cfg = Config(od1)
|
||||
cfg.property.read_write()
|
||||
cfg.option('ip_admin_eth0.ip_admin_eth0').value.set([undefined])
|
||||
cfg.option('ip_admin_eth0.ip_admin_eth0').value.set([None])
|
||||
assert cfg.option('ip_admin_eth0.ip_admin_eth0').value.get() == [None]
|
||||
assert cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get() == None
|
||||
compare(cfg.value.mandatory(), ['ip_admin_eth0.ip_admin_eth0'])
|
||||
|
@ -527,7 +518,7 @@ def test_mandatory_follower():
|
|||
cfg = Config(od1)
|
||||
cfg.property.read_only()
|
||||
assert cfg.option('ip_admin_eth0.ip_admin_eth0').value.get() == []
|
||||
assert cfg.value.dict() == {'ip_admin_eth0.ip_admin_eth0': []}
|
||||
assert parse_od_get(cfg.value.get()) == {'ip_admin_eth0.ip_admin_eth0': []}
|
||||
#
|
||||
cfg.property.read_write()
|
||||
cfg.option('ip_admin_eth0.ip_admin_eth0').value.set(['ip'])
|
||||
|
@ -548,7 +539,7 @@ def test_mandatory_follower():
|
|||
cfg.property.read_only()
|
||||
assert cfg.option('ip_admin_eth0.ip_admin_eth0').value.get() == ['ip']
|
||||
assert cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get() == 'ip'
|
||||
assert cfg.value.dict() == {'ip_admin_eth0.ip_admin_eth0': [{'ip_admin_eth0.ip_admin_eth0': 'ip', 'ip_admin_eth0.netmask_admin_eth0': 'ip'}]}
|
||||
assert parse_od_get(cfg.value.get()) == {'ip_admin_eth0.ip_admin_eth0': [{'ip_admin_eth0.ip_admin_eth0': 'ip', 'ip_admin_eth0.netmask_admin_eth0': 'ip'}]}
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
|
@ -570,7 +561,14 @@ def test_mandatory_warnings_follower():
|
|||
|
||||
|
||||
def test_mandatory_warnings_symlink():
|
||||
od1 = make_description_sym()
|
||||
stroption = StrOption('str', 'Test string option', default="abc",
|
||||
properties=('mandatory', ))
|
||||
stroption1 = StrOption('str1', 'Test string option',
|
||||
properties=('mandatory', ))
|
||||
stroption2 = SymLinkOption('unicode2', stroption1)
|
||||
stroption3 = StrOption('str3', 'Test string option', multi=True,
|
||||
properties=('mandatory', ))
|
||||
od1 = OptionDescription('tiram', '', [stroption, stroption1, stroption2, stroption3])
|
||||
cfg = Config(od1)
|
||||
cfg.option('str').value.set('')
|
||||
cfg.property.read_write()
|
||||
|
@ -683,32 +681,32 @@ def test_mandatory_od_disabled():
|
|||
# assert not list_sessions()
|
||||
|
||||
|
||||
def return_list(val=None, suffix=None):
|
||||
def return_list(val=None, identifier=None):
|
||||
if val:
|
||||
return val
|
||||
else:
|
||||
return ['val1', 'val2']
|
||||
|
||||
|
||||
def test_mandatory_dyndescription():
|
||||
st = StrOption('st', '', properties=('mandatory',))
|
||||
dod = DynOptionDescription('dod', '', [st], suffixes=Calculation(return_list))
|
||||
od = OptionDescription('od', '', [dod])
|
||||
od2 = OptionDescription('od', '', [od])
|
||||
cfg = Config(od2)
|
||||
cfg.property.read_only()
|
||||
compare(cfg.value.mandatory(), ['od.dodval1.stval1', 'od.dodval2.stval2'])
|
||||
|
||||
|
||||
def test_mandatory_dyndescription_context():
|
||||
val1 = StrOption('val1', '', ['val1', 'val2'], multi=True)
|
||||
st = StrOption('st', '', properties=('mandatory',))
|
||||
dod = DynOptionDescription('dod', '', [st], suffixes=Calculation(return_list, Params(ParamOption(val1))))
|
||||
od = OptionDescription('od', '', [dod, val1])
|
||||
od2 = OptionDescription('od', '', [od])
|
||||
cfg = Config(od2)
|
||||
cfg.property.read_only()
|
||||
compare(cfg.value.mandatory(), ['od.dodval1.stval1', 'od.dodval2.stval2'])
|
||||
#def test_mandatory_dyndescription():
|
||||
# st = StrOption('st', '', properties=('mandatory',))
|
||||
# dod = DynOptionDescription('dod', '', [st], identifiers=Calculation(return_list))
|
||||
# od = OptionDescription('od', '', [dod])
|
||||
# od2 = OptionDescription('od', '', [od])
|
||||
# cfg = Config(od2)
|
||||
# cfg.property.read_only()
|
||||
# compare(cfg.value.mandatory(), ['od.dodval1.st', 'od.dodval2.st'])
|
||||
#
|
||||
#
|
||||
#def test_mandatory_dyndescription_context():
|
||||
# val1 = StrOption('val1', '', ['val1', 'val2'], multi=True)
|
||||
# st = StrOption('st', '', properties=('mandatory',))
|
||||
# dod = DynOptionDescription('dod', '', [st], identifiers=Calculation(return_list, Params(ParamOption(val1))))
|
||||
# od = OptionDescription('od', '', [dod, val1])
|
||||
# od2 = OptionDescription('od', '', [od])
|
||||
# cfg = Config(od2)
|
||||
# cfg.property.read_only()
|
||||
# compare(cfg.value.mandatory(), ['od.dodval1.st', 'od.dodval2.st'])
|
||||
|
||||
|
||||
def test_mandatory_callback_leader_and_followers_leader():
|
||||
|
@ -723,3 +721,12 @@ def test_mandatory_callback_leader_and_followers_leader():
|
|||
# FIXME cfg = get_config(cfg, config_type)
|
||||
compare(cfg.value.mandatory(), ['val1.val1'])
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_mandatory_and_disabled():
|
||||
password = PasswordOption(name="password", doc="Password", properties=frozenset({"disabled"}))
|
||||
username = UsernameOption(name="username", doc="Username", properties=frozenset({"normal", Calculation(is_mandatory, Params((ParamOption(password)))), "disabled"}))
|
||||
od1 = OptionDescription('rootconfig', '', [username, password])
|
||||
cfg = Config(od1)
|
||||
cfg.property.read_write()
|
||||
cfg.value.get()
|
||||
|
|
|
@ -15,76 +15,76 @@ def make_metaconfig():
|
|||
return MetaConfig([], optiondescription=od2, name='metacfg1')
|
||||
|
||||
|
||||
def test_multi_parents_path():
|
||||
"""
|
||||
metacfg1 (1) ---
|
||||
| -- cfg1
|
||||
metacfg2 (2) ---
|
||||
"""
|
||||
metacfg1 = make_metaconfig()
|
||||
cfg1 = metacfg1.config.new(type='config', name="cfg1")
|
||||
metacfg2 = MetaConfig([cfg1], name='metacfg2')
|
||||
#def test_multi_parents_path():
|
||||
# """
|
||||
# metacfg1 (1) ---
|
||||
# | -- cfg1
|
||||
# metacfg2 (2) ---
|
||||
# """
|
||||
# metacfg1 = make_metaconfig()
|
||||
# cfg1 = metacfg1.config.new(type='config', name="cfg1")
|
||||
# metacfg2 = MetaConfig([cfg1], name='metacfg2')
|
||||
# #
|
||||
# assert metacfg1.config.path() == 'metacfg1'
|
||||
# assert metacfg2.config.path() == 'metacfg2'
|
||||
# assert cfg1.config.path() == 'metacfg2.metacfg1.cfg1'
|
||||
#
|
||||
assert metacfg1.config.path() == 'metacfg1'
|
||||
assert metacfg2.config.path() == 'metacfg2'
|
||||
assert cfg1.config.path() == 'metacfg2.metacfg1.cfg1'
|
||||
|
||||
|
||||
def test_multi_parents_path_same():
|
||||
"""
|
||||
--- metacfg2 (1) ---
|
||||
metacfg1 --| | -- cfg1
|
||||
--- metacfg3 (2) ---
|
||||
"""
|
||||
metacfg1 = make_metaconfig()
|
||||
metacfg2 = metacfg1.config.new(type='metaconfig', name="metacfg2")
|
||||
metacfg3 = metacfg1.config.new(type='metaconfig', name="metacfg3")
|
||||
cfg1 = metacfg2.config.new(type='config', name="cfg1")
|
||||
metacfg3.config.add(cfg1)
|
||||
#
|
||||
assert metacfg2.config.path() == 'metacfg1.metacfg2'
|
||||
assert metacfg3.config.path() == 'metacfg1.metacfg3'
|
||||
assert cfg1.config.path() == 'metacfg1.metacfg3.metacfg1.metacfg2.cfg1'
|
||||
metacfg1.option('od1.i1').value.set(1)
|
||||
metacfg3.option('od1.i1').value.set(2)
|
||||
assert cfg1.option('od1.i1').value.get() == 1
|
||||
orideep = cfg1.config.deepcopy(metaconfig_prefix="test_", name='test_cfg1')
|
||||
deep = orideep
|
||||
while True:
|
||||
try:
|
||||
children = list(deep.config.list())
|
||||
except:
|
||||
break
|
||||
assert len(children) < 2
|
||||
deep = children[0]
|
||||
assert deep.config.path() == 'test_metacfg3.test_metacfg1.test_metacfg2.test_cfg1'
|
||||
assert cfg1.option('od1.i1').value.get() == 1
|
||||
|
||||
|
||||
|
||||
def test_multi_parents_value():
|
||||
metacfg1 = make_metaconfig()
|
||||
cfg1 = metacfg1.config.new(type='config', name="cfg1")
|
||||
metacfg2 = MetaConfig([cfg1], name='metacfg2')
|
||||
#def test_multi_parents_path_same():
|
||||
# """
|
||||
# --- metacfg2 (1) ---
|
||||
# metacfg1 --| | -- cfg1
|
||||
# --- metacfg3 (2) ---
|
||||
# """
|
||||
# metacfg1 = make_metaconfig()
|
||||
# metacfg2 = metacfg1.config.new(type='metaconfig', name="metacfg2")
|
||||
# metacfg3 = metacfg1.config.new(type='metaconfig', name="metacfg3")
|
||||
# cfg1 = metacfg2.config.new(type='config', name="cfg1")
|
||||
# metacfg3.config.add(cfg1)
|
||||
# #
|
||||
# assert metacfg2.config.path() == 'metacfg1.metacfg2'
|
||||
# assert metacfg3.config.path() == 'metacfg1.metacfg3'
|
||||
# assert cfg1.config.path() == 'metacfg1.metacfg3.metacfg1.metacfg2.cfg1'
|
||||
# metacfg1.option('od1.i1').value.set(1)
|
||||
# metacfg3.option('od1.i1').value.set(2)
|
||||
# assert cfg1.option('od1.i1').value.get() == 1
|
||||
# orideep = cfg1.config.deepcopy(metaconfig_prefix="test_", name='test_cfg1')
|
||||
# deep = orideep
|
||||
# while True:
|
||||
# try:
|
||||
# children = list(deep.config.list())
|
||||
# except:
|
||||
# break
|
||||
# assert len(children) < 2
|
||||
# deep = children[0]
|
||||
# assert deep.config.path() == 'test_metacfg3.test_metacfg1.test_metacfg2.test_cfg1'
|
||||
# assert cfg1.option('od1.i1').value.get() == 1
|
||||
#
|
||||
assert cfg1.option('od1.i1').value.get() == None
|
||||
assert cfg1.option('od1.i2').value.get() == 1
|
||||
assert cfg1.option('od1.i3').value.get() == None
|
||||
#
|
||||
assert metacfg1.option('od1.i1').value.get() == None
|
||||
assert metacfg1.option('od1.i2').value.get() == 1
|
||||
assert metacfg1.option('od1.i3').value.get() == None
|
||||
#
|
||||
assert metacfg2.option('od1.i1').value.get() == None
|
||||
assert metacfg2.option('od1.i2').value.get() == 1
|
||||
assert metacfg2.option('od1.i3').value.get() == None
|
||||
#
|
||||
metacfg1.option('od1.i3').value.set(3)
|
||||
assert metacfg1.option('od1.i3').value.get() == 3
|
||||
assert cfg1.option('od1.i3').value.get() == 3
|
||||
assert metacfg2.option('od1.i2').value.get() == 1
|
||||
#
|
||||
metacfg2.option('od1.i2').value.set(4)
|
||||
assert metacfg2.option('od1.i2').value.get() == 4
|
||||
assert metacfg1.option('od1.i2').value.get() == 1
|
||||
assert cfg1.option('od1.i2').value.get() == 4
|
||||
#def test_multi_parents_value():
|
||||
# metacfg1 = make_metaconfig()
|
||||
# cfg1 = metacfg1.config.new(type='config', name="cfg1")
|
||||
# metacfg2 = MetaConfig([cfg1], name='metacfg2')
|
||||
# #
|
||||
# assert cfg1.option('od1.i1').value.get() == None
|
||||
# assert cfg1.option('od1.i2').value.get() == 1
|
||||
# assert cfg1.option('od1.i3').value.get() == None
|
||||
# #
|
||||
# assert metacfg1.option('od1.i1').value.get() == None
|
||||
# assert metacfg1.option('od1.i2').value.get() == 1
|
||||
# assert metacfg1.option('od1.i3').value.get() == None
|
||||
# #
|
||||
# assert metacfg2.option('od1.i1').value.get() == None
|
||||
# assert metacfg2.option('od1.i2').value.get() == 1
|
||||
# assert metacfg2.option('od1.i3').value.get() == None
|
||||
# #
|
||||
# metacfg1.option('od1.i3').value.set(3)
|
||||
# assert metacfg1.option('od1.i3').value.get() == 3
|
||||
# assert cfg1.option('od1.i3').value.get() == 3
|
||||
# assert metacfg2.option('od1.i2').value.get() == 1
|
||||
# #
|
||||
# metacfg2.option('od1.i2').value.set(4)
|
||||
# assert metacfg2.option('od1.i2').value.get() == 4
|
||||
# assert metacfg1.option('od1.i2').value.get() == 1
|
||||
# assert cfg1.option('od1.i2').value.get() == 4
|
||||
|
|
|
@ -20,7 +20,7 @@ def a_func():
|
|||
return None
|
||||
|
||||
|
||||
def display_name(*args):
|
||||
def display_name(*args, with_quote=False):
|
||||
return 'display_name'
|
||||
|
||||
|
||||
|
@ -34,20 +34,6 @@ def test_option_valid_name():
|
|||
i = SymLinkOption("test1", i)
|
||||
|
||||
|
||||
def test_option_get_information():
|
||||
description = "it's ok"
|
||||
string = 'some informations'
|
||||
i = IntOption('test', description)
|
||||
with pytest.raises(ValueError):
|
||||
i.impl_get_information('noinfo')
|
||||
i.impl_set_information('info', string)
|
||||
assert i.impl_get_information('info') == string
|
||||
with pytest.raises(ValueError):
|
||||
i.impl_get_information('noinfo')
|
||||
assert i.impl_get_information('noinfo', 'default') == 'default'
|
||||
assert i.impl_get_information('doc') == description
|
||||
|
||||
|
||||
def test_option_get_information_config():
|
||||
description = "it's ok"
|
||||
string = 'some informations'
|
||||
|
@ -55,21 +41,16 @@ def test_option_get_information_config():
|
|||
od = OptionDescription('od', '', [i])
|
||||
cfg = Config(od)
|
||||
with pytest.raises(ValueError):
|
||||
i.impl_get_information('noinfo')
|
||||
with pytest.raises(AttributeError):
|
||||
i.impl_set_information('info', string)
|
||||
with pytest.raises(ValueError):
|
||||
i.impl_get_information('noinfo')
|
||||
assert i.impl_get_information('noinfo', 'default') == 'default'
|
||||
assert i.impl_get_information('doc') == description
|
||||
cfg.option('test').information.get('noinfo')
|
||||
assert cfg.option('test').information.get('noinfo', 'default') == 'default'
|
||||
assert cfg.option('test').information.get('doc') == description
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_option_unknown():
|
||||
description = "it's ok"
|
||||
string = 'some informations'
|
||||
i = IntOption('test', description)
|
||||
i.impl_set_information('noinfo', 'optdefault')
|
||||
i = IntOption('test', description, informations={'noinfo': 'optdefault'})
|
||||
od = OptionDescription('od', '', [i])
|
||||
cfg = Config(od)
|
||||
#
|
||||
|
@ -93,8 +74,7 @@ def test_option_description():
|
|||
def test_option_get_information_default():
|
||||
description = "it's ok"
|
||||
string = 'some informations'
|
||||
i = IntOption('test', description)
|
||||
i.impl_set_information('noinfo', 'optdefault')
|
||||
i = IntOption('test', description, informations={'noinfo': 'optdefault'})
|
||||
od = OptionDescription('od', '', [i])
|
||||
cfg = Config(od)
|
||||
#
|
||||
|
@ -108,32 +88,30 @@ def test_option_get_information_default():
|
|||
def test_option_get_information_config2():
|
||||
description = "it's ok"
|
||||
string = 'some informations'
|
||||
i = IntOption('test', description)
|
||||
i.impl_set_information('info', string)
|
||||
i = IntOption('test', description, informations={'info': string})
|
||||
od = OptionDescription('od', '', [i])
|
||||
cfg = Config(od)
|
||||
with pytest.raises(ValueError):
|
||||
i.impl_get_information('noinfo')
|
||||
with pytest.raises(AttributeError):
|
||||
i.impl_set_information('info', 'hello')
|
||||
assert i.impl_get_information('info') == string
|
||||
cfg.option('test').information.get('noinfo')
|
||||
assert cfg.option('test').information.get('info') == string
|
||||
with pytest.raises(ValueError):
|
||||
i.impl_get_information('noinfo')
|
||||
assert i.impl_get_information('noinfo', 'default') == 'default'
|
||||
assert i.impl_get_information('doc') == description
|
||||
cfg.option('test').information.get('noinfo')
|
||||
assert cfg.option('test').information.get('noinfo', 'default') == 'default'
|
||||
assert cfg.option('test').information.get('doc') == description
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_optiondescription_get_information():
|
||||
description = "it's ok"
|
||||
string = 'some informations'
|
||||
o = OptionDescription('test', description, [])
|
||||
o.impl_set_information('info', string)
|
||||
assert o.impl_get_information('info') == string
|
||||
o = OptionDescription('test', description, [], informations={'info': string})
|
||||
od = OptionDescription('od', '', [o])
|
||||
cfg = Config(od)
|
||||
assert cfg.option('test').information.get('info') == string
|
||||
with pytest.raises(ValueError):
|
||||
o.impl_get_information('noinfo')
|
||||
assert o.impl_get_information('noinfo', 'default') == 'default'
|
||||
assert o.impl_get_information('doc') == description
|
||||
cfg.option('test').information.get('noinfo')
|
||||
assert cfg.option('test').information.get('noinfo', 'default') == 'default'
|
||||
assert cfg.option('test').information.get('doc') == description
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
|
@ -202,24 +180,9 @@ def test_optiondescription_list():
|
|||
od2 = OptionDescription('od', '', [od1, od3])
|
||||
od4 = OptionDescription('od', '', [od2])
|
||||
cfg = Config(od4)
|
||||
assert len(list(cfg.option('od').list('option'))) == 0
|
||||
assert len(list(cfg.option('od').list('optiondescription'))) == 2
|
||||
assert len(list(cfg.option('od').list('optiondescription', group_type=groups.family))) == 1
|
||||
assert len(list(cfg.option('od').list('optiondescription', group_type=groups.notfamily1))) == 1
|
||||
assert len(list(cfg.option('od.od').list('option'))) == 1
|
||||
assert len(list(cfg.option('od.od2').list('option'))) == 1
|
||||
try:
|
||||
list(cfg.option('od').list('unknown'))
|
||||
except AssertionError:
|
||||
pass
|
||||
else:
|
||||
raise Exception('must raise')
|
||||
try:
|
||||
list(cfg.option('od').list('option', group_type='toto'))
|
||||
except AssertionError:
|
||||
pass
|
||||
else:
|
||||
raise Exception('must raise')
|
||||
assert len(list(cfg.option('od').list())) == 2
|
||||
assert len(list(cfg.option('od.od').list())) == 1
|
||||
assert len(list(cfg.option('od.od2').list())) == 1
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
|
@ -233,22 +196,7 @@ def test_optiondescription_group():
|
|||
od3.impl_set_group_type(groups.notfamily)
|
||||
od2 = OptionDescription('od', '', [od1, od3])
|
||||
cfg = Config(od2)
|
||||
assert len(list(cfg.option.list('option'))) == 0
|
||||
assert len(list(cfg.option.list('optiondescription'))) == 2
|
||||
assert len(list(cfg.option.list('optiondescription', group_type=groups.family))) == 1
|
||||
assert len(list(cfg.option.list('optiondescription', group_type=groups.notfamily))) == 1
|
||||
try:
|
||||
list(cfg.option.list('unknown'))
|
||||
except AssertionError:
|
||||
pass
|
||||
else:
|
||||
raise Exception('must raise')
|
||||
try:
|
||||
list(cfg.option.list('option', group_type='toto'))
|
||||
except AssertionError:
|
||||
pass
|
||||
else:
|
||||
raise Exception('must raise')
|
||||
assert len(list(cfg.list())) == 2
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
|
@ -351,4 +299,4 @@ def test_option_display_name():
|
|||
display_name=display_name,
|
||||
)
|
||||
assert cfg.option('test1').name() == 'test1'
|
||||
assert cfg.option('test1').doc() == 'display_name'
|
||||
assert cfg.option('test1').description() == 'display_name'
|
||||
|
|
|
@ -13,7 +13,7 @@ from tiramisu import ChoiceOption, BoolOption, IntOption, FloatOption, \
|
|||
valid_ip_netmask, ParamSelfOption, ParamInformation, ParamSelfInformation
|
||||
from tiramisu.error import PropertiesOptionError, ConflictError, LeadershipError, ConfigError
|
||||
from tiramisu.i18n import _
|
||||
from .config import config_type, get_config
|
||||
from .config import config_type, get_config, parse_od_get
|
||||
|
||||
|
||||
def return_val():
|
||||
|
@ -289,16 +289,34 @@ def test_callback(config_type):
|
|||
cfg = Config(od1)
|
||||
cfg.property.read_write()
|
||||
cfg = get_config(cfg, config_type)
|
||||
assert isinstance(cfg.option('val1').value.get(uncalculated=True), Calculation)
|
||||
assert cfg.option('val1').value.get() == 'val'
|
||||
cfg.option('val1').value.set('new-val')
|
||||
assert cfg.option('val1').value.get() == 'new-val'
|
||||
with pytest.raises(ConfigError):
|
||||
assert cfg.option('val1').defaultmulti() == None
|
||||
assert cfg.option('val1').value.defaultmulti() == None
|
||||
cfg.option('val1').value.reset()
|
||||
assert cfg.option('val1').value.get() == 'val'
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_callback_set(config_type):
|
||||
val1 = StrOption('val1', "")
|
||||
val2 = StrOption('val2', "")
|
||||
od1 = OptionDescription('rootconfig', '', [val1, val2])
|
||||
cfg = Config(od1)
|
||||
cfg.property.read_write()
|
||||
cfg = get_config(cfg, config_type)
|
||||
cfg.option('val2').value.set(Calculation(return_value, Params(ParamOption(val1))))
|
||||
assert cfg.option('val2').value.get() == None
|
||||
#
|
||||
cfg.option('val1').value.set('new-val')
|
||||
assert cfg.option('val2').value.get() == 'new-val'
|
||||
#
|
||||
cfg.option('val1').value.reset()
|
||||
assert cfg.option('val2').value.get() == None
|
||||
|
||||
|
||||
def test_params():
|
||||
with pytest.raises(ValueError):
|
||||
Params('str')
|
||||
|
@ -398,8 +416,7 @@ def test_callback_information(config_type):
|
|||
|
||||
def test_callback_information2(config_type):
|
||||
val1 = StrOption('val1', "", Calculation(return_value, Params(ParamSelfInformation('information', 'no_value'))))
|
||||
val2 = StrOption('val2', "", Calculation(return_value, Params(ParamSelfInformation('information'))))
|
||||
val2.impl_set_information('information', 'new_value')
|
||||
val2 = StrOption('val2', "", Calculation(return_value, Params(ParamSelfInformation('information'))), informations={'information': 'new_value'})
|
||||
val3 = StrOption('val3', "", Calculation(return_value, Params(ParamSelfInformation('information'))))
|
||||
od1 = OptionDescription('rootconfig', '', [val1, val2, val3])
|
||||
cfg = Config(od1)
|
||||
|
@ -415,9 +432,8 @@ def test_callback_information2(config_type):
|
|||
|
||||
|
||||
def test_callback_information3(config_type):
|
||||
val1 = StrOption('val1', "")
|
||||
val1 = StrOption('val1', "", informations={'information': 'new_value'})
|
||||
val2 = StrOption('val2', "", Calculation(return_value, Params(ParamInformation('information', option=val1))))
|
||||
val1.impl_set_information('information', 'new_value')
|
||||
od1 = OptionDescription('rootconfig', '', [val1, val2])
|
||||
cfg = Config(od1)
|
||||
cfg.property.read_write()
|
||||
|
@ -532,7 +548,7 @@ def test_callback_multi(config_type):
|
|||
cfg = get_config(cfg, config_type)
|
||||
assert cfg.option('val1').value.get() == ['val']
|
||||
cfg.option('val1').value.set(['new-val'])
|
||||
assert cfg.option('val1').defaultmulti() == None
|
||||
assert cfg.option('val1').value.defaultmulti() == None
|
||||
assert cfg.option('val1').value.get() == ['new-val']
|
||||
cfg.option('val1').value.set(['new-val', 'new-val2'])
|
||||
assert cfg.option('val1').value.get() == ['new-val', 'new-val2']
|
||||
|
@ -541,6 +557,21 @@ def test_callback_multi(config_type):
|
|||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_callback_multi_set(config_type):
|
||||
val1 = StrOption('val1', "", multi=True)
|
||||
od1 = OptionDescription('rootconfig', '', [val1])
|
||||
cfg = Config(od1)
|
||||
cfg.property.read_write()
|
||||
cfg = get_config(cfg, config_type)
|
||||
assert cfg.option('val1').value.get() == []
|
||||
#
|
||||
cfg.option('val1').value.set([Calculation(return_val)])
|
||||
assert cfg.option('val1').value.get() == ['val']
|
||||
cfg.option('val1').value.reset()
|
||||
assert cfg.option('val1').value.get() == []
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_callback_multi_value(config_type):
|
||||
val1 = StrOption('val1', "", ['val'], multi=True)
|
||||
option = ParamOption(val1)
|
||||
|
@ -577,6 +608,30 @@ def test_callback_multi_value(config_type):
|
|||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_callback_multi_value_set(config_type):
|
||||
val1 = StrOption('val1', "", ['val1'], multi=True)
|
||||
val2 = StrOption('val2', "", ['val2'], multi=True)
|
||||
od1 = OptionDescription('rootconfig', '', [val1, val2])
|
||||
cfg = Config(od1)
|
||||
cfg.property.read_write()
|
||||
cfg = get_config(cfg, config_type)
|
||||
assert cfg.option('val1').value.get() == ['val1']
|
||||
assert cfg.option('val2').value.get() == ['val2']
|
||||
#
|
||||
cfg.option('val2').value.set(Calculation(return_value, Params(ParamOption(val1))))
|
||||
assert cfg.option('val1').value.get() == ['val1']
|
||||
assert cfg.option('val2').value.get() == ['val1']
|
||||
#
|
||||
cfg.option('val1').value.set(['val1', 'yes'])
|
||||
assert cfg.option('val2').value.get() == ['val1', 'yes']
|
||||
assert cfg.option('val2').value.get() == ['val1', 'yes']
|
||||
#
|
||||
cfg.option('val2').value.reset()
|
||||
assert cfg.option('val1').value.get() == ['val1', 'yes']
|
||||
assert cfg.option('val2').value.get() == ['val2']
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_callback_multi_list(config_type):
|
||||
val1 = StrOption('val1', "", Calculation(return_list), multi=True, properties=('notunique',))
|
||||
od1 = OptionDescription('rootconfig', '', [val1])
|
||||
|
@ -611,7 +666,7 @@ def test_callback_multi_callback(config_type):
|
|||
cfg.property.read_write()
|
||||
cfg = get_config(cfg, config_type)
|
||||
assert cfg.option('val1.val1').value.get() == ['val']
|
||||
cfg.option('val1.val1').value.set(['val1', undefined])
|
||||
cfg.option('val1.val1').value.set(['val1', None])
|
||||
assert cfg.option('val1.val1').value.get() == ['val1', None]
|
||||
# assert not list_sessions()
|
||||
|
||||
|
@ -624,24 +679,50 @@ def test_callback_multi_callback_default(config_type):
|
|||
cfg.property.read_write()
|
||||
cfg = get_config(cfg, config_type)
|
||||
assert cfg.option('val1.val1').value.get() == []
|
||||
cfg.option('val1.val1').value.set(['val1', undefined])
|
||||
cfg.option('val1.val1').value.set(['val1', 'val'])
|
||||
assert cfg.option('val1.val1').value.get() == ['val1', 'val']
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_callback_leader_and_followers_leader(config_type):
|
||||
val1 = StrOption('val1', "", default=[Calculation(return_val)], default_multi=Calculation(return_val), multi=True, properties=('notunique',))
|
||||
val2 = StrOption('val2', "", multi=True)
|
||||
interface1 = Leadership('val1', '', [val1, val2])
|
||||
od1 = OptionDescription('rootconfig', '', [interface1])
|
||||
val1 = StrOption('val1', "", default=['val'], multi=True)
|
||||
val2 = StrOption('val2', "", default=Calculation(return_value, Params(ParamOption(val1))), default_multi=Calculation(return_val), multi=True, properties=('notunique',))
|
||||
val3 = StrOption('val3', "", multi=True)
|
||||
interface1 = Leadership('val2', '', [val2, val3])
|
||||
od1 = OptionDescription('rootconfig', '', [val1, interface1])
|
||||
cfg = Config(od1)
|
||||
cfg.property.read_write()
|
||||
cfg = get_config(cfg, config_type)
|
||||
assert cfg.option('val1.val1').value.get() == ['val']
|
||||
cfg.option('val1.val1').value.set([undefined, undefined])
|
||||
assert cfg.option('val1.val1').value.get() == ['val', 'val']
|
||||
assert cfg.option('val1.val2', 0).value.get() == None
|
||||
assert cfg.option('val1.val2', 1).value.get() == None
|
||||
assert cfg.option('val1').value.get() == ['val']
|
||||
assert cfg.option('val2.val2').value.get() == ['val']
|
||||
#
|
||||
cfg.option('val1').value.set(['val1', 'val2'])
|
||||
assert cfg.option('val1').value.get() == ['val1', 'val2']
|
||||
assert cfg.option('val2.val2').value.get() == ['val1', 'val2']
|
||||
assert cfg.option('val2.val3', 0).value.get() == None
|
||||
assert cfg.option('val2.val3', 1).value.get() == None
|
||||
#
|
||||
cfg.option('val1').value.set(['val'])
|
||||
assert cfg.option('val2.val2').value.get() == ['val']
|
||||
assert cfg.option('val2.val3', 0).value.get() == None
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_callback_leader_and_followers_leader_set(config_type):
|
||||
val1 = StrOption('val1', "", default=['val1', 'val2'], multi=True)
|
||||
val2 = StrOption('val2', "", multi=True)
|
||||
val3 = StrOption('val3', "", multi=True)
|
||||
interface1 = Leadership('val2', '', [val2, val3])
|
||||
od1 = OptionDescription('rootconfig', '', [val1, interface1])
|
||||
cfg = Config(od1)
|
||||
cfg.property.read_write()
|
||||
cfg = get_config(cfg, config_type)
|
||||
assert cfg.option('val1').value.get() == ['val1', 'val2']
|
||||
#
|
||||
cfg.option('val2.val2').value.set(Calculation(return_value, Params(ParamOption(val1))))
|
||||
assert cfg.option('val2.val2').value.get() == ['val1', 'val2']
|
||||
assert cfg.option('val2.val3', 0).value.get() == None
|
||||
assert cfg.option('val2.val3', 1).value.get() == None
|
||||
#assert not list_sessions()
|
||||
|
||||
|
||||
|
@ -670,6 +751,28 @@ def test_callback_follower(config_type):
|
|||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_callback_follower_set(config_type):
|
||||
val1 = StrOption('val1', "")
|
||||
val2 = StrOption('val2', "", default=['val1'], multi=True)
|
||||
val3 = StrOption('val3', "", multi=True)
|
||||
interface1 = Leadership('val2', '', [val2, val3])
|
||||
od1 = OptionDescription('rootconfig', '', [val1, interface1])
|
||||
cfg = Config(od1)
|
||||
cfg.property.read_write()
|
||||
cfg = get_config(cfg, config_type)
|
||||
cfg.option('val1').value.set('val')
|
||||
assert cfg.option('val1').value.get() == 'val'
|
||||
assert cfg.option('val2.val2').value.get() == ['val1']
|
||||
assert cfg.option('val2.val3', 0).value.get() == None
|
||||
#
|
||||
cfg.option('val2.val3', 0).value.set(Calculation(return_value, Params(ParamOption(val1))))
|
||||
assert cfg.option('val2.val3', 0).value.get() == 'val'
|
||||
#
|
||||
cfg.option('val1').value.set('val1')
|
||||
assert cfg.option('val2.val3', 0).value.get() == 'val1'
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_callback_leader_and_followers_leader2(config_type):
|
||||
val1 = StrOption('val1', "", multi=True)
|
||||
val2 = StrOption('val2', "", multi=True, default_multi='val2')
|
||||
|
@ -704,7 +807,7 @@ def test_callback_leader_and_followers_leader_mandatory1(config_type):
|
|||
cfg.send()
|
||||
cfg_ori.property.read_write()
|
||||
cfg = get_config(cfg_ori, config_type)
|
||||
cfg.option('val1.val1').value.set([undefined, 'val3'])
|
||||
cfg.option('val1.val1').value.set(['val', 'val3'])
|
||||
if config_type == 'tiramisu-api':
|
||||
cfg.send()
|
||||
cfg_ori.property.read_only()
|
||||
|
@ -842,7 +945,7 @@ def test_consistency_leader_and_followers_leader_mandatory_transitive():
|
|||
try:
|
||||
cfg.option('val1.val2', 0).value.get()
|
||||
except PropertiesOptionError as error:
|
||||
assert str(error) == str(_('cannot access to {0} "{1}" because has {2} {3}').format('option', 'val2', _('property'), '"disabled"'))
|
||||
assert str(error) == str(_('cannot access to {0} {1} because has {2} {3}').format('option', '"val2"', _('property'), '"disabled"'))
|
||||
else:
|
||||
raise Exception('must raises')
|
||||
assert list(cfg.value.mandatory()) == []
|
||||
|
@ -860,7 +963,8 @@ def test_callback_leader_and_followers_leader_list(config_type):
|
|||
assert cfg.option('val1.val1').value.get() == ['val', 'val']
|
||||
assert cfg.option('val1.val2', 0).value.get() == None
|
||||
assert cfg.option('val1.val2', 1).value.get() == None
|
||||
cfg.option('val1.val1').value.set(['val', 'val', undefined])
|
||||
default_multi = cfg.option('val1.val1').value.defaultmulti()
|
||||
cfg.option('val1.val1').value.set(['val', 'val', default_multi])
|
||||
assert cfg.option('val1.val1').value.get() == ['val', 'val', None]
|
||||
assert cfg.option('val1.val2', 0).value.get() == None
|
||||
assert cfg.option('val1.val2', 1).value.get() == None
|
||||
|
@ -1398,6 +1502,22 @@ def test_leadership_callback_description(config_type):
|
|||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_leadership_callback_outside(config_type):
|
||||
st1 = StrOption('st1', "", multi=True)
|
||||
st2 = StrOption('st2', "", multi=True, default_multi='val2')
|
||||
stm = Leadership('st1', '', [st1, st2])
|
||||
st3 = StrOption('st3', "", Calculation(return_value, Params(ParamOption(st2))), multi=True)
|
||||
st = OptionDescription('st', '', [stm, st3])
|
||||
od = OptionDescription('od', '', [st])
|
||||
od2 = OptionDescription('od', '', [od])
|
||||
cfg = Config(od2)
|
||||
cfg = get_config(cfg, config_type)
|
||||
owner = cfg.owner.get()
|
||||
cfg.option('od.st.st1.st1').value.set(['yes'])
|
||||
assert parse_od_get(cfg.value.get()) == {'od.st.st1.st1': [{'od.st.st1.st1': 'yes', 'od.st.st1.st2': 'val2'}], 'od.st.st3': ['val2']}
|
||||
## assert not list_sessions()
|
||||
|
||||
|
||||
def test_callback_raise():
|
||||
opt1 = BoolOption('opt1', 'Option 1', Calculation(return_raise))
|
||||
opt2 = BoolOption('opt2', 'Option 2', Calculation(return_valueerror))
|
||||
|
@ -1423,7 +1543,7 @@ def test_calc_value_simple(config_type):
|
|||
od1 = OptionDescription('root', '', [val1, val2])
|
||||
cfg = Config(od1)
|
||||
cfg = get_config(cfg, config_type)
|
||||
assert cfg.value.dict() == {'val1': 'val1', 'val2': 'val1'}
|
||||
assert parse_od_get(cfg.value.get()) == {'val1': 'val1', 'val2': 'val1'}
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
|
@ -1434,7 +1554,7 @@ def test_calc_value_multi(config_type):
|
|||
od1 = OptionDescription('root', '', [val1, val2, val3])
|
||||
cfg = Config(od1)
|
||||
cfg = get_config(cfg, config_type)
|
||||
assert cfg.value.dict() == {'val1': 'val1', 'val2': 'val2', 'val3': ['val1', 'val2']}
|
||||
assert parse_od_get(cfg.value.get()) == {'val1': 'val1', 'val2': 'val2', 'val3': ['val1', 'val2']}
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
|
@ -1444,9 +1564,9 @@ def test_calc_value_disabled():
|
|||
od1 = OptionDescription('root', '', [val1, val2])
|
||||
cfg = Config(od1)
|
||||
cfg.property.read_write()
|
||||
assert cfg.value.dict() == {'val1': 'val1', 'val2': 'val1'}
|
||||
assert parse_od_get(cfg.value.get()) == {'val1': 'val1', 'val2': 'val1'}
|
||||
cfg.option('val1').property.add('disabled')
|
||||
assert cfg.value.dict() == {'val2': 'default_value'}
|
||||
assert parse_od_get(cfg.value.get()) == {'val2': 'default_value'}
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
|
@ -1461,9 +1581,9 @@ def test_calc_value_condition(config_type):
|
|||
cfg = Config(od1)
|
||||
cfg.property.read_write()
|
||||
cfg = get_config(cfg, config_type)
|
||||
assert cfg.value.dict() == {'boolean': True, 'val1': 'val1', 'val2': 'val1'}
|
||||
assert parse_od_get(cfg.value.get()) == {'boolean': True, 'val1': 'val1', 'val2': 'val1'}
|
||||
cfg.option('boolean').value.set(False)
|
||||
assert cfg.value.dict() == {'boolean': False, 'val1': 'val1', 'val2': 'default_value'}
|
||||
assert parse_od_get(cfg.value.get()) == {'boolean': False, 'val1': 'val1', 'val2': 'default_value'}
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
|
@ -1474,7 +1594,7 @@ def test_calc_value_allow_none(config_type):
|
|||
od1 = OptionDescription('root', '', [val1, val2, val3])
|
||||
cfg = Config(od1)
|
||||
cfg = get_config(cfg, config_type)
|
||||
assert cfg.value.dict() == {'val1': 'val1', 'val2': None, 'val3': ['val1', None]}
|
||||
assert parse_od_get(cfg.value.get()) == {'val1': 'val1', 'val2': None, 'val3': ['val1', None]}
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
|
@ -1485,7 +1605,7 @@ def test_calc_value_remove_duplicate(config_type):
|
|||
od1 = OptionDescription('root', '', [val1, val2, val3])
|
||||
cfg = Config(od1)
|
||||
cfg = get_config(cfg, config_type)
|
||||
assert cfg.value.dict() == {'val1': 'val1', 'val2': 'val1', 'val3': ['val1']}
|
||||
assert parse_od_get(cfg.value.get()) == {'val1': 'val1', 'val2': 'val1', 'val3': ['val1']}
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
|
@ -1496,7 +1616,7 @@ def test_calc_value_remove_duplicate2(config_type):
|
|||
od1 = OptionDescription('root', '', [val1, val2, val3])
|
||||
cfg = Config(od1)
|
||||
cfg = get_config(cfg, config_type)
|
||||
assert cfg.value.dict() == {'val1': ['val1', 'val1'], 'val2': ['val1', 'val1'], 'val3': ['val1-val1']}
|
||||
assert parse_od_get(cfg.value.get()) == {'val1': ['val1', 'val1'], 'val2': ['val1', 'val1'], 'val3': ['val1-val1']}
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
|
@ -1508,9 +1628,9 @@ def test_calc_value_join(config_type):
|
|||
od1 = OptionDescription('root', '', [val1, val2, val3, val4])
|
||||
cfg = Config(od1)
|
||||
cfg = get_config(cfg, config_type)
|
||||
assert cfg.value.dict() == {'val1': 'val1', 'val2': 'val2', 'val3': None, 'val4': None}
|
||||
assert parse_od_get(cfg.value.get()) == {'val1': 'val1', 'val2': 'val2', 'val3': None, 'val4': None}
|
||||
cfg.option('val3').value.set('val3')
|
||||
assert cfg.value.dict() == {'val1': 'val1', 'val2': 'val2', 'val3': 'val3', 'val4': 'val1.val2.val3'}
|
||||
assert parse_od_get(cfg.value.get()) == {'val1': 'val1', 'val2': 'val2', 'val3': 'val3', 'val4': 'val1.val2.val3'}
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
|
@ -1520,9 +1640,9 @@ def test_calc_value_join_multi(config_type):
|
|||
od1 = OptionDescription('root', '', [val1, val4])
|
||||
cfg = Config(od1)
|
||||
cfg = get_config(cfg, config_type)
|
||||
assert cfg.value.dict() == {'val1': [], 'val4': []}
|
||||
assert parse_od_get(cfg.value.get()) == {'val1': [], 'val4': []}
|
||||
cfg.option('val1').value.set(['val1'])
|
||||
assert cfg.value.dict() == {'val1': ['val1'], 'val4': ['val1']}
|
||||
assert parse_od_get(cfg.value.get()) == {'val1': ['val1'], 'val4': ['val1']}
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
|
@ -1534,9 +1654,9 @@ def test_calc_value_join_multi_value(config_type):
|
|||
od1 = OptionDescription('root', '', [val1, val2, val3, val4])
|
||||
cfg = Config(od1)
|
||||
cfg = get_config(cfg, config_type)
|
||||
assert cfg.value.dict() == {'val1': ['val1'], 'val2': ['val2'], 'val3': [None], 'val4': []}
|
||||
assert parse_od_get(cfg.value.get()) == {'val1': ['val1'], 'val2': ['val2'], 'val3': [None], 'val4': []}
|
||||
cfg.option('val3').value.set(['val3'])
|
||||
assert cfg.value.dict() == {'val1': ['val1'], 'val2': ['val2'], 'val3': ['val3'], 'val4': ['val1.val2.val3']}
|
||||
assert parse_od_get(cfg.value.get()) == {'val1': ['val1'], 'val2': ['val2'], 'val3': ['val3'], 'val4': ['val1.val2.val3']}
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
|
@ -1548,9 +1668,9 @@ def test_calc_value_min():
|
|||
od1 = OptionDescription('root', '', [val1, val2, val3, val4])
|
||||
cfg = Config(od1)
|
||||
cfg.property.read_write()
|
||||
assert cfg.value.dict() == {'val1': 'val1', 'val2': 'val2', 'val3': 'val3', 'val4': 'val1.val2.val3'}
|
||||
assert parse_od_get(cfg.value.get()) == {'val1': 'val1', 'val2': 'val2', 'val3': 'val3', 'val4': 'val1.val2.val3'}
|
||||
cfg.option('val3').property.add('disabled')
|
||||
assert cfg.value.dict() == {'val1': 'val1', 'val2': 'val2', 'val4': ''}
|
||||
assert parse_od_get(cfg.value.get()) == {'val1': 'val1', 'val2': 'val2', 'val4': ''}
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
|
@ -1561,7 +1681,7 @@ def test_calc_value_add(config_type):
|
|||
od1 = OptionDescription('root', '', [val1, val2, val3])
|
||||
cfg = Config(od1)
|
||||
cfg = get_config(cfg, config_type)
|
||||
assert cfg.value.dict() == {'val1': 1, 'val2': 2, 'val3': 3}
|
||||
assert parse_od_get(cfg.value.get()) == {'val1': 1, 'val2': 2, 'val3': 3}
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
|
@ -1586,3 +1706,38 @@ def test_calc_dependencies(config_type):
|
|||
def test_callback__kwargs_wrong(config_type):
|
||||
with pytest.raises(ValueError):
|
||||
Params(kwargs='string')
|
||||
|
||||
|
||||
def test_callback_information_parent(config_type):
|
||||
information = ParamInformation('information')
|
||||
val1 = StrOption('val1', "", Calculation(return_value, Params(information)))
|
||||
od2 = OptionDescription('od', '', [val1], informations={'information': 'new_value'})
|
||||
information.set_option(od2)
|
||||
od1 = OptionDescription('rootconfig', '', [od2])
|
||||
cfg = Config(od1)
|
||||
cfg.property.read_write()
|
||||
cfg = get_config(cfg, config_type)
|
||||
assert cfg.option('od.val1').value.get() == 'new_value'
|
||||
cfg.option('od').information.set('information', 'new_value2')
|
||||
assert cfg.option('od.val1').value.get() == 'new_value2'
|
||||
|
||||
|
||||
def test_callback_information_redefined(config_type):
|
||||
val1 = StrOption('val1', "")
|
||||
information = ParamInformation('information', option=val1)
|
||||
val2 = StrOption('val2', "", Calculation(return_value, Params(information)))
|
||||
od2 = OptionDescription('od', '', [val1, val2], informations={'information': 'new_value'})
|
||||
with pytest.raises(ConfigError):
|
||||
information.set_option(od2)
|
||||
|
||||
|
||||
def test_callback_information_redefined_after(config_type):
|
||||
information = ParamInformation('information')
|
||||
val1 = StrOption('val1', "", Calculation(return_value, Params(information)))
|
||||
od2 = OptionDescription('od', '', [val1], informations={'information': 'new_value'})
|
||||
od1 = OptionDescription('rootconfig', '', [od2])
|
||||
cfg = Config(od1)
|
||||
cfg.property.read_write()
|
||||
cfg = get_config(cfg, config_type)
|
||||
with pytest.raises(ConfigError):
|
||||
information.set_option(od2)
|
||||
|
|
|
@ -115,8 +115,10 @@ def test_force_default_on_freeze_multi():
|
|||
cfg_ori.property.read_write()
|
||||
cfg = cfg_ori
|
||||
# FIXME cfg = get_config(cfg_ori, config_type)
|
||||
cfg.option('dummy1').value.set([undefined, True])
|
||||
cfg.option('dummy2').value.set([undefined, False])
|
||||
default = cfg.option('dummy1').value.default()[0]
|
||||
cfg.option('dummy1').value.set([default, True])
|
||||
default = cfg.option('dummy2').value.default()[0]
|
||||
cfg.option('dummy2').value.set([default, False])
|
||||
owner = cfg.owner.get()
|
||||
assert cfg.option('dummy1').owner.get() == owner
|
||||
assert cfg.option('dummy2').owner.get() == owner
|
||||
|
@ -144,45 +146,23 @@ def test_force_default_on_freeze_multi():
|
|||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_force_default_on_freeze_leader():
|
||||
dummy1 = BoolOption('dummy1', 'Test int option', multi=True, properties=('force_default_on_freeze',))
|
||||
dummy2 = BoolOption('dummy2', 'Test string option', multi=True)
|
||||
descr = Leadership("dummy1", "", [dummy1, dummy2])
|
||||
od1 = OptionDescription("root", "", [descr])
|
||||
with pytest.raises(ConfigError):
|
||||
Config(od1)
|
||||
#def test_force_default_on_freeze_leader():
|
||||
# dummy1 = BoolOption('dummy1', 'Test int option', multi=True, properties=('force_default_on_freeze',))
|
||||
# dummy2 = BoolOption('dummy2', 'Test string option', multi=True)
|
||||
# descr = Leadership("dummy1", "", [dummy1, dummy2])
|
||||
# od1 = OptionDescription("root", "", [descr])
|
||||
# with pytest.raises(ConfigError):
|
||||
# Config(od1)
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_force_metaconfig_on_freeze_leader():
|
||||
dummy1 = BoolOption('dummy1', 'Test int option', multi=True, properties=('force_metaconfig_on_freeze',))
|
||||
dummy2 = BoolOption('dummy2', 'Test string option', multi=True)
|
||||
descr = Leadership("dummy1", "", [dummy1, dummy2])
|
||||
od1 = OptionDescription("root", "", [descr])
|
||||
with pytest.raises(ConfigError):
|
||||
Config(od1)
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_force_default_on_freeze_leader_frozen():
|
||||
dummy1 = BoolOption('dummy1', 'Test int option', multi=True, properties=('force_default_on_freeze', 'frozen'))
|
||||
dummy2 = BoolOption('dummy2', 'Test string option', multi=True)
|
||||
descr = Leadership("dummy1", "", [dummy1, dummy2])
|
||||
od1 = OptionDescription("root", "", [descr])
|
||||
cfg = Config(od1)
|
||||
with pytest.raises(LeadershipError):
|
||||
cfg.option('dummy1.dummy1').property.remove('frozen')
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
def test_force_metaconfig_on_freeze_leader_frozen():
|
||||
dummy1 = BoolOption('dummy1', 'Test int option', multi=True, properties=('force_metaconfig_on_freeze', 'frozen'))
|
||||
dummy2 = BoolOption('dummy2', 'Test string option', multi=True)
|
||||
descr = Leadership("dummy1", "", [dummy1, dummy2])
|
||||
od1 = OptionDescription("root", "", [descr])
|
||||
cfg = Config(od1)
|
||||
with pytest.raises(LeadershipError):
|
||||
cfg.option('dummy1.dummy1').property.remove('frozen')
|
||||
#def test_force_metaconfig_on_freeze_leader():
|
||||
# dummy1 = BoolOption('dummy1', 'Test int option', multi=True, properties=('force_metaconfig_on_freeze',))
|
||||
# dummy2 = BoolOption('dummy2', 'Test string option', multi=True)
|
||||
# descr = Leadership("dummy1", "", [dummy1, dummy2])
|
||||
# od1 = OptionDescription("root", "", [descr])
|
||||
# with pytest.raises(ConfigError):
|
||||
# Config(od1)
|
||||
# assert not list_sessions()
|
||||
|
||||
|
||||
|
|