rst to md doc conversion
This commit is contained in:
parent
026e665ab0
commit
ece7537b89
19 changed files with 2721 additions and 9 deletions
|
@ -1,5 +1,9 @@
|
|||
LICENSES
|
||||
---------
|
||||
![Logo Tiramisu](logo.png "logo Tiramisu")
|
||||
|
||||
[Documentations](doc/README.md)
|
||||
|
||||
|
||||
# LICENSES
|
||||
|
||||
See COPYING for the licences of the code and the documentation.
|
||||
|
64
doc/README.md
Normal file
64
doc/README.md
Normal file
|
@ -0,0 +1,64 @@
|
|||
![Logo Tiramisu](../logo.png "logo Tiramisu")
|
||||
|
||||
# Python3 Tiramisu library user documentation
|
||||
|
||||
## The tasting of `Tiramisu` --- `user documentation`
|
||||
|
||||
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.
|
||||
|
||||
- [Getting started](gettingstarted.md)
|
||||
- [The Config](config.md)
|
||||
- [Browse the Config](browse.md)
|
||||
- [Manage values](api_value.md)
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
api_property
|
||||
storage
|
||||
application
|
||||
quiz
|
||||
glossary
|
||||
|
||||
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"
|
||||
|
||||
|
||||
.. FIXME changer le display_name !
|
||||
.. FIXME voir si warnings_only dans validator !
|
||||
.. FIXME submulti dans les leadership
|
||||
.. FIXME exemple avec default_multi (et undefined)
|
||||
.. FIXME config, metaconfig, ...
|
||||
.. FIXME fonction de base
|
||||
.. FIXME information
|
||||
.. FIXME demoting_error_warning, warnings, ...
|
||||
.. FIXME class _TiramisuOptionOptionDescription(CommonTiramisuOption):
|
||||
.. FIXME class _TiramisuOptionOption(_TiramisuOptionOptionDescription):
|
||||
.. FIXME class TiramisuOptionInformation(CommonTiramisuOption):
|
||||
.. FIXME class TiramisuContextInformation(TiramisuConfig):
|
||||
.. FIXME expire
|
||||
.. FIXME custom display_name
|
||||
.. FIXME assert await cfg.cache.get_expiration_time() == 5
|
||||
.. FIXME await cfg.cache.set_expiration_time(1)
|
||||
.. FIXME convert_suffix_to_path
|
||||
|
||||
|
||||
|
||||
Indices and full bunch of code
|
||||
===============================
|
||||
|
||||
|
||||
* `All files for which code is available <_modules/index.html>`_
|
||||
* :ref:`genindex`
|
||||
* :ref:`search`
|
439
doc/api_value.md
Normal file
439
doc/api_value.md
Normal file
|
@ -0,0 +1,439 @@
|
|||
# Manage values
|
||||
|
||||
## Values with options
|
||||
|
||||
### Simple option
|
||||
|
||||
Begin by creating a Config. This Config will contains two options:
|
||||
|
||||
- first one is an option where the user will set an unix path
|
||||
- second one is an option that calculate the disk usage of the previous unix path
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
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')
|
||||
```
|
||||
|
||||
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()))])
|
||||
```
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
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))))
|
||||
```
|
||||
|
||||
Finally add those options in option description and a Config:
|
||||
|
||||
```
|
||||
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()
|
||||
|
||||
config = run(main())
|
||||
```
|
||||
|
||||
#### 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:
|
||||
|
||||
```
|
||||
None
|
||||
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())
|
||||
|
||||
run(main())
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```
|
||||
/
|
||||
668520882176.0
|
||||
```
|
||||
|
||||
When you enter a value it is validated:
|
||||
|
||||
>>> 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
|
||||
|
||||
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:
|
||||
|
||||
>>> config.option('disk.path').value.valid()
|
||||
True
|
||||
|
||||
#### Display the default value
|
||||
|
||||
Even if the value is modify, you can display the default value with `default` method:
|
||||
|
||||
>>> config.option('disk.path').value.set('/')
|
||||
>>> config.option('disk.usage').value.set(1.0)
|
||||
>>> config.option('disk.usage').value.get()
|
||||
1.0
|
||||
>>> config.option('disk.usage').value.default()
|
||||
668510105600.0
|
||||
|
||||
#### Return to the default value
|
||||
|
||||
If the value is modified, just `reset` it to retrieve the default value:
|
||||
|
||||
>>> config.option('disk.path').value.set('/')
|
||||
>>> config.option('disk.path').value.get()
|
||||
/
|
||||
>>> config.option('disk.path').value.reset()
|
||||
>>> config.option('disk.path').value.get()
|
||||
None
|
||||
|
||||
#### The ownership of a value
|
||||
|
||||
Every option has an owner, that will indicate who changed the option's value last.
|
||||
|
||||
The default owner of every option is "default", and means that the value is the default one.
|
||||
|
||||
If you use a "reset" instruction to get back to the default value, the owner will get back
|
||||
to "default" as well.
|
||||
|
||||
>>> config.option('disk.path').value.reset()
|
||||
>>> config.option('disk.path').owner.isdefault()
|
||||
True
|
||||
>>> config.option('disk.path').owner.get()
|
||||
default
|
||||
>>> config.option('disk.path').value.set('/')
|
||||
>>> config.option('disk.path').owner.isdefault()
|
||||
False
|
||||
>>> config.option('disk.path').owner.get()
|
||||
user
|
||||
|
||||
All modified values have an owner. We can change at anytime this owner:
|
||||
|
||||
>>> config.option('disk.path').owner.set('itsme')
|
||||
>>> config.option('disk.path').owner.get()
|
||||
itsme
|
||||
|
||||
.. note::
|
||||
This will work only if the current owner isn't "default".
|
||||
|
||||
This new user will be keep until anyone change the value:
|
||||
|
||||
>>> config.option('disk.path').value.set('/')
|
||||
>>> config.option('disk.path').owner.get()
|
||||
user
|
||||
|
||||
This username is in fact the `config` user, which is `user` by default:
|
||||
|
||||
>>> config.owner.get()
|
||||
user
|
||||
|
||||
This owner will be the owner that all the options in the config will get when their value is changed.
|
||||
|
||||
This explains why earlier, the owner became "user" when changing the option's value.
|
||||
|
||||
We can change this owner:
|
||||
|
||||
>>> config.owner.set('itsme')
|
||||
>>> config.option('disk.path').value.set('/')
|
||||
>>> config.option('disk.path').owner.get()
|
||||
itsme
|
||||
|
||||
### 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
|
||||
: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>`
|
||||
|
||||
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
|
||||
|
||||
.. FIXME undefined
|
||||
|
||||
For multi option, just modify a little bit the previous example.
|
||||
The user can, now, set multiple path.
|
||||
|
||||
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
|
||||
:lines: 23-25
|
||||
:linenos:
|
||||
|
||||
Secondly, the function calc_disk_usage must return a list:
|
||||
|
||||
.. literalinclude:: ../src/api_value_multi.py
|
||||
:lines: 11-26
|
||||
:linenos:
|
||||
|
||||
Finally `usage` option is also a multi:
|
||||
|
||||
.. literalinclude:: ../src/api_value_multi.py
|
||||
:lines: 27-30
|
||||
:linenos:
|
||||
|
||||
:download:`download the config <../src/api_value_multi.py>`
|
||||
|
||||
#### Get or set a multi value
|
||||
|
||||
Since the options are multi, the default value is a list:
|
||||
|
||||
>>> config.option('disk.path').value.get()
|
||||
[]
|
||||
>>> config.option('disk.usage').value.get()
|
||||
[]
|
||||
|
||||
A multi option waiting for a list:
|
||||
|
||||
>>> config.option('disk.path').value.set(['/', '/tmp'])
|
||||
>>> config.option('disk.path').value.get()
|
||||
['/', '/tmp']
|
||||
>>> config.option('disk.usage').value.get()
|
||||
[668499898368.0, 8279277568.0]
|
||||
|
||||
#### The ownership of multi option
|
||||
|
||||
There is no difference in behavior between a simple option and a multi option:
|
||||
|
||||
>>> config.option('disk.path').value.reset()
|
||||
>>> config.option('disk.path').owner.isdefault()
|
||||
True
|
||||
>>> config.option('disk.path').owner.get()
|
||||
default
|
||||
>>> config.option('disk.path').value.set(['/', '/tmp'])
|
||||
>>> config.option('disk.path').owner.get()
|
||||
user
|
||||
|
||||
### Leadership
|
||||
|
||||
In previous example, we cannot define different `size_type` for each path. If you want do this, you need a leadership.
|
||||
|
||||
In this case, each time we add a path, we can change an associate `size_type`.
|
||||
|
||||
As each value of followers are isolate, the function `calc_disk_usage` will receive only one path and one size.
|
||||
|
||||
So let's change this function:
|
||||
|
||||
.. literalinclude:: ../src/api_value_leader.py
|
||||
:lines: 12-18
|
||||
:linenos:
|
||||
|
||||
Secondly the option `size_type` became a multi:
|
||||
|
||||
.. literalinclude:: ../src/api_value_leader.py
|
||||
:lines: 24-25
|
||||
:linenos:
|
||||
|
||||
Finally disk has to be a leadership:
|
||||
|
||||
.. literalinclude:: ../src/api_value_leader.py
|
||||
:lines: 30
|
||||
:linenos:
|
||||
|
||||
#### Get and set a leader
|
||||
|
||||
A leader is, in fact, a multi option:
|
||||
|
||||
>>> config.option('disk.path').value.set(['/', '/tmp'])
|
||||
>>> config.option('disk.path').value.get()
|
||||
['/', '/tmp']
|
||||
|
||||
There is two differences:
|
||||
|
||||
- we can get the leader length:
|
||||
|
||||
>>> config.option('disk.path').value.set(['/', '/tmp'])
|
||||
>>> config.option('disk.path').value.len()
|
||||
2
|
||||
|
||||
- we cannot reduce by assignation a leader:
|
||||
|
||||
>>> config.option('disk.path').value.set(['/', '/tmp'])
|
||||
>>> from tiramisu.error import LeadershipError
|
||||
>>> try:
|
||||
... config.option('disk.path').value.set(['/'])
|
||||
... except LeadershipError as err:
|
||||
... print(err)
|
||||
cannot reduce length of the leader "Path"
|
||||
|
||||
We cannot reduce a leader because Tiramisu cannot determine which isolate follower we have to remove, this first one or the second one?
|
||||
|
||||
To reduce use the `pop` method:
|
||||
|
||||
>>> config.option('disk.path').value.set(['/', '/tmp'])
|
||||
>>> config.option('disk.path').value.pop(1)
|
||||
>>> config.option('disk.path').value.get()
|
||||
['/']
|
||||
|
||||
#### Get and set a follower
|
||||
|
||||
As followers are isolate, we cannot get all the follower values:
|
||||
|
||||
>>> config.option('disk.path').value.set(['/', '/tmp'])
|
||||
>>> from tiramisu.error import APIError
|
||||
>>> try:
|
||||
... config.option('disk.size_type').value.get()
|
||||
... except APIError as err:
|
||||
... print(err)
|
||||
index must be set with the follower option "Size type"
|
||||
|
||||
Index is mandatory:
|
||||
|
||||
>>> config.option('disk.path').value.set(['/', '/tmp'])
|
||||
>>> config.option('disk.size_type', 0).value.get()
|
||||
bytes
|
||||
>>> config.option('disk.size_type', 1).value.get()
|
||||
bytes
|
||||
|
||||
It's the same thing during the assignment:
|
||||
|
||||
>>> config.option('disk.path').value.set(['/', '/tmp'])
|
||||
>>> config.option('disk.size_type', 0).value.set('giga bytes')
|
||||
|
||||
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
|
||||
|
||||
There is no differences between a multi option and a leader option:
|
||||
|
||||
>>> config.option('disk.path').value.set(['/', '/tmp'])
|
||||
>>> config.option('disk.path').owner.get()
|
||||
user
|
||||
|
||||
For follower, it's different, always because followers are isolate:
|
||||
|
||||
>>> config.option('disk.size_type', 0).value.set('giga bytes')
|
||||
>>> config.option('disk.size_type', 0).owner.isdefault()
|
||||
False
|
||||
>>> config.option('disk.size_type', 0).owner.get()
|
||||
user
|
||||
>>> config.option('disk.size_type', 1).owner.isdefault()
|
||||
True
|
||||
>>> config.option('disk.size_type', 1).owner.get()
|
||||
default
|
||||
|
||||
## 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)
|
||||
{'disk.path': ['/', '/tmp'], 'disk.size_type': ['giga bytes', 'bytes'], 'disk.usage': [622.578239440918, 8279273472.0]}
|
||||
|
||||
## Values in config
|
||||
|
||||
###dict
|
||||
|
||||
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()
|
||||
{'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)
|
||||
{'path': ['/', '/tmp'], 'size_type': ['giga bytes', 'bytes'], 'usage': [622.578239440918, 8279273472.0]}
|
||||
|
||||
### importation/exportation
|
||||
|
||||
In config, we can export full values:
|
||||
|
||||
>>> config.value.exportation()
|
||||
[['disk.path', 'disk.size_type'], [None, [0]], [['/', '/tmp'], ['giga bytes']], ['user', ['user']]]
|
||||
|
||||
and reimport it later:
|
||||
|
||||
>>> export = config.value.exportation()
|
||||
>>> 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.
|
||||
|
350
doc/browse.md
Normal file
350
doc/browse.md
Normal file
|
@ -0,0 +1,350 @@
|
|||
# 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
Normal file
107
doc/config.md
Normal file
|
@ -0,0 +1,107 @@
|
|||
# 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)
|
BIN
doc/config.png
Normal file
BIN
doc/config.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
57
doc/dynoptiondescription.md
Normal file
57
doc/dynoptiondescription.md
Normal file
|
@ -0,0 +1,57 @@
|
|||
# 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).
|
||||
|
42
doc/gettingstarted.md
Normal file
42
doc/gettingstarted.md
Normal file
|
@ -0,0 +1,42 @@
|
|||
# Getting started
|
||||
|
||||
## 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
|
||||
options to where they are actually used and even more annoying to add new
|
||||
options.
|
||||
|
||||
To circumvent these problems the configuration control was introduced.
|
||||
|
||||
## 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
|
||||
rules and the fact that the whole consistency is preserved at any time.
|
||||
|
||||
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
|
||||
|
||||
The best way is to use the python [pip](https://pip.pypa.io/en/stable/installing/) installer
|
||||
|
||||
And then type:
|
||||
|
||||
```bash
|
||||
$ pip install tiramisu
|
||||
```
|
||||
|
||||
### Advanced users
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
This will get you a fresh checkout of the code repository in a local directory
|
||||
named "tiramisu".
|
45
doc/leadership.md
Normal file
45
doc/leadership.md
Normal file
|
@ -0,0 +1,45 @@
|
|||
# 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
Normal file
134
doc/option.md
Normal file
|
@ -0,0 +1,134 @@
|
|||
# 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)
|
35
doc/optiondescription.md
Normal file
35
doc/optiondescription.md
Normal file
|
@ -0,0 +1,35 @@
|
|||
# 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).
|
||||
|
485
doc/options.md
Normal file
485
doc/options.md
Normal file
|
@ -0,0 +1,485 @@
|
|||
# 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')
|
||||
```
|
||||
|
||||
# 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')
|
||||
```
|
||||
|
||||
|
183
doc/own_option.md
Normal file
183
doc/own_option.md
Normal file
|
@ -0,0 +1,183 @@
|
|||
# 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
Normal file
109
doc/property.md
Normal file
|
@ -0,0 +1,109 @@
|
|||
# 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.
|
13
doc/symlinkoption.md
Normal file
13
doc/symlinkoption.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
# 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
Normal file
494
doc/validator.md
Normal file
|
@ -0,0 +1,494 @@
|
|||
# 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())
|
||||
```
|
BIN
logo.png
Normal file
BIN
logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.6 KiB |
140
logo.svg
Normal file
140
logo.svg
Normal file
|
@ -0,0 +1,140 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="18.86796mm"
|
||||
height="25.206835mm"
|
||||
viewBox="0 0 18.867959 25.206835"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
|
||||
sodipodi:docname="logo.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.404458"
|
||||
inkscape:cx="1.3622562"
|
||||
inkscape:cy="68.453372"
|
||||
inkscape:window-width="1033"
|
||||
inkscape:window-height="1080"
|
||||
inkscape:window-x="26"
|
||||
inkscape:window-y="23"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-78.220996,-39.872698)">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:2.52546px;line-height:1;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;stroke-width:0.631367"
|
||||
x="93.453247"
|
||||
y="48.8326"
|
||||
id="text18049"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan18047"
|
||||
x="93.453247"
|
||||
y="48.8326"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.52546px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';text-align:center;text-anchor:middle;stroke-width:0.631367">I</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="93.453247"
|
||||
y="51.358059"
|
||||
id="tspan18051"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.52546px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';text-align:center;text-anchor:middle;stroke-width:0.631367">R</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="93.453247"
|
||||
y="53.883518"
|
||||
id="tspan21745"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.52546px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';text-align:center;text-anchor:middle;stroke-width:0.631367">A</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="93.453247"
|
||||
y="56.408981"
|
||||
id="tspan21747"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.52546px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';text-align:center;text-anchor:middle;stroke-width:0.631367">M</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="93.453247"
|
||||
y="58.934441"
|
||||
id="tspan21749"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.52546px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';text-align:center;text-anchor:middle;stroke-width:0.631367">I</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="93.453247"
|
||||
y="61.4599"
|
||||
id="tspan21751"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.52546px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';text-align:center;text-anchor:middle;stroke-width:0.631367">S</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="93.453247"
|
||||
y="63.985359"
|
||||
id="tspan21753"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.52546px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';text-align:center;text-anchor:middle;stroke-width:0.631367">U</tspan></text>
|
||||
<path
|
||||
style="fill:#2a80af;fill-opacity:1;stroke:#000000;stroke-width:0.315683;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 81.95784,40.030698 13.710125,-1.58e-4 c 0.510538,0.02425 0.694831,0.0683 0.982497,0.311837 0.203261,0.250591 0.249844,0.413553 0.280237,0.871344 l -1.6e-5,2.550089 c 0,0 0.02416,0.6051 -0.27347,0.902731 -0.297626,0.297631 -0.46168,0.339646 -0.989248,0.360002 l -2.315853,1.55e-4 v -2.498 H 81.95784 Z"
|
||||
id="path6247"
|
||||
sodipodi:nodetypes="cccccsccccc" />
|
||||
<ellipse
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.315695;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:7.7;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
|
||||
id="path4757"
|
||||
cx="95.089363"
|
||||
cy="-42.528553"
|
||||
transform="scale(1,-1)"
|
||||
rx="0.61845386"
|
||||
ry="0.61849999" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.315683;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 93.352109,45.026698 -13.710129,1.58e-4 c -0.51053,-0.02425 -0.69483,-0.0683 -0.98249,-0.311836 -0.20326,-0.250591 -0.24985,-0.413553 -0.28024,-0.871344 l 2e-5,-2.550089 c 0,0 -0.0242,-0.6051 0.27347,-0.902732 0.29762,-0.297631 0.46168,-0.339646 0.98924,-0.360002 l 2.31586,-1.55e-4 v 2.498 h 11.394269 z"
|
||||
id="path6247-36"
|
||||
sodipodi:nodetypes="cccccsccccc" />
|
||||
<ellipse
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.315695;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:7.7;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
|
||||
id="path4757-7"
|
||||
cx="-80.220589"
|
||||
cy="42.528843"
|
||||
transform="scale(-1,1)"
|
||||
rx="0.61845386"
|
||||
ry="0.61849999" />
|
||||
<g
|
||||
id="g2099"
|
||||
transform="rotate(-90,84.548611,28.232765)">
|
||||
<path
|
||||
style="fill:#2a80af;fill-opacity:1;stroke:#000000;stroke-width:0.315683;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 51.438689,29.084802 13.710125,-1.58e-4 c 0.510538,0.02425 0.694831,0.0683 0.982497,0.311837 0.203261,0.250591 0.249844,0.413553 0.280237,0.871344 l -1.6e-5,2.550089 c 0,0 0.02416,0.6051 -0.27347,0.902731 -0.297626,0.297631 -0.46168,0.339646 -0.989248,0.360002 l -2.315853,1.55e-4 v -2.498 H 51.438689 Z"
|
||||
id="path6247-5"
|
||||
sodipodi:nodetypes="cccccsccccc" />
|
||||
<ellipse
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.315695;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:7.7;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
|
||||
id="path4757-3"
|
||||
cx="64.570213"
|
||||
cy="-31.582657"
|
||||
transform="scale(1,-1)"
|
||||
rx="0.61845386"
|
||||
ry="0.61849999" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.315683;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 62.832958,34.080802 -13.710129,1.58e-4 c -0.51053,-0.02425 -0.69483,-0.0683 -0.98249,-0.311836 -0.20326,-0.250591 -0.24985,-0.413553 -0.28024,-0.871344 l 2e-5,-2.550089 c 0,0 -0.0242,-0.6051 0.27347,-0.902732 0.29762,-0.297631 0.46168,-0.339646 0.98924,-0.360002 l 2.31586,-1.55e-4 v 2.498 h 11.394269 z"
|
||||
id="path6247-36-5"
|
||||
sodipodi:nodetypes="cccccsccccc" />
|
||||
<ellipse
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.315695;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:7.7;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
|
||||
id="path4757-7-6"
|
||||
cx="-49.701439"
|
||||
cy="31.582947"
|
||||
transform="scale(-1,1)"
|
||||
rx="0.61845386"
|
||||
ry="0.61849999" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 7.8 KiB |
|
@ -14,7 +14,7 @@
|
|||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ____________________________________________________________
|
||||
from inspect import ismethod, getdoc, signature
|
||||
from inspect import ismethod, getdoc, signature, iscoroutinefunction
|
||||
from time import time
|
||||
from typing import List, Set, Any, Optional, Callable, Union, Dict
|
||||
from warnings import catch_warnings, simplefilter
|
||||
|
@ -71,6 +71,9 @@ class TiramisuHelp:
|
|||
display(_('Commands:'))
|
||||
for module_name in modules:
|
||||
module = getattr(self, module_name)
|
||||
if not ('__getattr__' in dir(module) and iscoroutinefunction(module.__getattr__)) and \
|
||||
hasattr(module, '__name__') and module.__name__ == 'wrapped':
|
||||
module = module.func
|
||||
doc = _(getdoc(module))
|
||||
display(self._tmpl_help.format(module_name, doc).expandtabs(max_len + 10))
|
||||
display()
|
||||
|
@ -231,6 +234,7 @@ def option_and_connection(func):
|
|||
ret = await func(self, *args, **kwargs)
|
||||
del config_bag.connection
|
||||
return ret
|
||||
wrapped.func = func
|
||||
return wrapped
|
||||
|
||||
|
||||
|
@ -661,6 +665,7 @@ def option_type(typ):
|
|||
ret = await func(*args, **kwargs)
|
||||
del config_bag.connection
|
||||
return ret
|
||||
wrapped.func = func
|
||||
return wrapped
|
||||
return wrapper
|
||||
|
||||
|
@ -865,7 +870,7 @@ class TiramisuOption(CommonTiramisu, TiramisuConfig):
|
|||
value=undefined,
|
||||
type=None,
|
||||
first: bool=False):
|
||||
"""find an option by name (only for optiondescription)"""
|
||||
"""Find an option by name (only for optiondescription)"""
|
||||
if not first:
|
||||
ret = []
|
||||
option = self._option_bag.option
|
||||
|
@ -967,7 +972,7 @@ class TiramisuOption(CommonTiramisu, TiramisuConfig):
|
|||
remotable: str="minimum",
|
||||
form: List=[],
|
||||
force: bool=False) -> Dict:
|
||||
"""convert config and option to tiramisu format"""
|
||||
"""Convert config and option to tiramisu format"""
|
||||
if force or self._tiramisu_dict is None:
|
||||
await self._load_dict(clearable, remotable)
|
||||
return await self._tiramisu_dict.todict(form)
|
||||
|
@ -975,7 +980,7 @@ class TiramisuOption(CommonTiramisu, TiramisuConfig):
|
|||
@option_type('optiondescription')
|
||||
async def updates(self,
|
||||
body: List) -> Dict:
|
||||
"""updates value with tiramisu format"""
|
||||
"""Updates value with tiramisu format"""
|
||||
if self._tiramisu_dict is None:
|
||||
await self._load_dict()
|
||||
return await self._tiramisu_dict.set_updates(body)
|
||||
|
@ -989,6 +994,7 @@ def connection(func):
|
|||
ret = await func(self, *args, **kwargs)
|
||||
del config_bag.connection
|
||||
return ret
|
||||
wrapped.func = func
|
||||
return wrapped
|
||||
|
||||
|
||||
|
@ -1447,14 +1453,14 @@ class TiramisuContextOption(TiramisuConfig, _TiramisuOptionWalk):
|
|||
remotable="minimum",
|
||||
form=[],
|
||||
force=False):
|
||||
"""convert config and option to tiramisu format"""
|
||||
"""Convert config and option to tiramisu format"""
|
||||
if force or self._tiramisu_dict is None:
|
||||
await self._load_dict(clearable, remotable)
|
||||
return await self._tiramisu_dict.todict(form)
|
||||
|
||||
async def updates(self,
|
||||
body: List) -> Dict:
|
||||
"""updates value with tiramisu format"""
|
||||
"""Updates value with tiramisu format"""
|
||||
if self._tiramisu_dict is None:
|
||||
await self._load_dict()
|
||||
return await self._tiramisu_dict.set_updates(body)
|
||||
|
@ -1565,7 +1571,7 @@ class _TiramisuContextGroupConfig(TiramisuConfig):
|
|||
|
||||
def __call__(self,
|
||||
path: Optional[str]):
|
||||
"""select a child Tiramisu config"""
|
||||
"""Select a child Tiramisu config"""
|
||||
spaths = path.split('.')
|
||||
config = self._config_bag.context
|
||||
for spath in spaths:
|
||||
|
@ -1689,14 +1695,19 @@ class _TiramisuContextMetaConfig(_TiramisuContextMixConfig):
|
|||
|
||||
|
||||
class TiramisuContextCache(TiramisuConfig):
|
||||
"""Manage config cache"""
|
||||
|
||||
async def reset(self):
|
||||
"""Reset cache"""
|
||||
await self._config_bag.context.cfgimpl_reset_cache(None, None)
|
||||
|
||||
async def set_expiration_time(self,
|
||||
time: int) -> None:
|
||||
"""Change expiration time value"""
|
||||
self._config_bag.expiration_time = time
|
||||
|
||||
async def get_expiration_time(self) -> int:
|
||||
"""Get expiration time value"""
|
||||
return self._config_bag.expiration_time
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue