Compare commits

...

38 commits

Author SHA1 Message Date
7fca8e9d62 bump: version 5.2.0a10 → 5.2.0a11 2025-09-29 11:02:23 +02:00
9c97f92775 fix: add min_integer and max_integer for intOption 2025-09-23 20:32:24 +02:00
7173a93749 bump: version 5.2.0a9 → 5.2.0a10 2025-09-20 18:26:39 +02:00
e351da2069 feat: better conflict with dynamic name 2025-09-20 18:16:43 +02:00
47c413e60c feat: reactive metaconfig 2025-09-11 22:07:53 +02:00
ac35f2428b fix: add dependency for calculation in choice values 2025-05-23 17:58:35 +02:00
ebc9779173 fix: better port validation with the value '' 2025-05-23 17:56:58 +02:00
379630fc38 bump: version 5.2.0a8 → 5.2.0a9 2025-05-12 09:12:51 +02:00
65b8fb014e fix: upgrade translation 2025-05-12 09:05:06 +02:00
fcbb9c6812 fix: better error message 2025-05-12 08:53:39 +02:00
c14a34f232 bump: version 5.2.0a7 → 5.2.0a8 2025-05-05 08:44:59 +02:00
db1b8dd48a bump: version 5.2.0a6 → 5.2.0a7 2025-05-05 08:42:04 +02:00
5ed902a372 fix: better error message 2025-05-05 08:39:43 +02:00
c54f7cb775 bump: version 5.2.0a5 → 5.2.0a6 2025-04-30 09:12:27 +02:00
3aaebfa34e fix: update translation 2025-04-29 23:02:13 +02:00
1a8ad3d06e fix: better error message 2025-04-29 22:56:34 +02:00
c24b0642a7 feat: ability to redefine ConfigError message 2025-04-27 14:46:22 +02:00
7ecec88881 fix: do not modify the default_multi attribute 2025-04-21 19:45:14 +02:00
3d8eb2c80d bump: version 5.2.0a4 → 5.2.0a5 2025-04-09 21:16:49 +02:00
3fadb1f6c3 fix: version 2025-04-09 21:16:39 +02:00
65b156c47d bump: version 5.2.0a3 → 5.2.0a4 2025-04-09 08:28:06 +02:00
db82dd6d41 fix: better error message 2025-04-09 08:27:38 +02:00
888ba21551 bump: version 5.2.0a2 → 5.2.0a3 2025-03-19 09:58:14 +01:00
b8899c98b1 fix: better errors message 2025-03-19 09:57:03 +01:00
1b640689b8 bump: version 5.2.0a1 → 5.2.0a2 2025-02-13 22:11:53 +01:00
adf94e6b15 feat: an option could be not validate 2025-02-13 22:11:26 +01:00
2f0e1fcb0c bump: version 5.2.0a0 → 5.2.0a1 2025-02-10 09:11:13 +01:00
6e4b22aea2 feat: can get params value easily 2025-02-10 08:45:21 +01:00
90947b5578 feat: ability to resolve domainname 2025-02-07 08:01:59 +01:00
f33713231e feat: better error messages 2025-02-07 07:46:53 +01:00
b443771c30 bump: version 5.1.1a1 → 5.2.0a0 2025-01-04 17:39:41 +01:00
6181b728ad fix: message error improvment 2025-01-04 17:39:25 +01:00
4cba819e0a feat: return index in error message 2025-01-04 17:39:11 +01:00
42471d42b7 feat: option.type can return the symlink type 2025-01-04 17:36:44 +01:00
0c993eddb0 bump: version 5.1.1a0 → 5.1.1a1 2024-12-11 21:58:17 +01:00
22fdabb6c0 fix: option.get now accept allow_dynoption 2024-12-11 21:57:46 +01:00
46a27e3a5c bump: version 5.1.0 → 5.1.1a0 2024-11-25 09:02:41 +01:00
baf1245c7a fix: dynoption could be optional if identifier is unknown 2024-11-25 09:00:00 +01:00
48 changed files with 4723 additions and 3874 deletions

File diff suppressed because it is too large Load diff

View file

@ -5,7 +5,7 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2024-11-05 08:52+0100\n"
"POT-Creation-Date: 2025-09-19 22:05+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -15,308 +15,404 @@ msgstr ""
"Generated-By: pygettext.py 1.5\n"
#: tiramisu/api.py:79
#: tiramisu/api.py:86
msgid "Settings:"
msgstr ""
#: tiramisu/api.py:83
#: tiramisu/api.py:90
msgid "Access to option without verifying permissive properties"
msgstr ""
#: tiramisu/api.py:88
#: tiramisu/api.py:95
msgid "Access to option without property restriction"
msgstr ""
#: tiramisu/api.py:93
#: tiramisu/api.py:100
msgid "Do not warnings during validation"
msgstr ""
#: tiramisu/api.py:97
#: tiramisu/api.py:104
msgid "Commands:"
msgstr ""
#: tiramisu/api.py:111 tiramisu/api.py:1840
#: tiramisu/api.py:118 tiramisu/api.py:1955
msgid "please specify a valid sub function ({0}.{1})"
msgstr ""
#: tiramisu/api.py:194
#: tiramisu/api.py:206
msgid "please do not specify index ({0}.{1})"
msgstr ""
#: tiramisu/api.py:199 tiramisu/api.py:844
#: tiramisu/api.py:211 tiramisu/api.py:940
msgid "please specify index with a follower option ({0}.{1})"
msgstr ""
#: tiramisu/api.py:220
#: tiramisu/api.py:234
msgid "please specify a valid sub function ({0}.{1}): {2}"
msgstr ""
#: tiramisu/api.py:431
#: tiramisu/api.py:525
msgid "the option {0} is not a dynamic option, cannot get identifiers with only_self parameter to True"
msgstr ""
#: tiramisu/api.py:517
#: tiramisu/api.py:613
msgid "cannot get option from a follower symlink without index"
msgstr ""
#: tiramisu/api.py:592
#: tiramisu/api.py:691
msgid "cannot add this property: \"{0}\""
msgstr ""
#: tiramisu/api.py:619
#: tiramisu/api.py:718
msgid "cannot remove option's property \"{0}\", use permissive instead in option \"{1}\""
msgstr ""
#: tiramisu/api.py:623
#: tiramisu/api.py:722
msgid "cannot find \"{0}\" in option \"{1}\""
msgstr ""
#: tiramisu/api.py:628
#: tiramisu/api.py:727
msgid "cannot remove option's property \"{0}\", use permissive instead in option \"{1}\" at index \"{2}\""
msgstr ""
#: tiramisu/api.py:632
#: tiramisu/api.py:731
msgid "cannot find \"{0}\" in option \"{1}\" at index \"{2}\""
msgstr ""
#: tiramisu/api.py:676
#: tiramisu/api.py:775
msgid "cannot find \"{0}\""
msgstr ""
#: tiramisu/api.py:808
msgid "cannot reduce length of the leader {}"
msgstr ""
#: tiramisu/api.py:861
#: tiramisu/api.py:957
msgid "only multi value has defaultmulti"
msgstr ""
#: tiramisu/api.py:1020
#: tiramisu/api.py:1121
msgid "please specify a valid sub function ({0}.{1}) for {2}"
msgstr ""
#: tiramisu/api.py:1407
#: tiramisu/api.py:1520
msgid "properties must be a frozenset"
msgstr ""
#: tiramisu/api.py:1411 tiramisu/api.py:1438
#: tiramisu/api.py:1524 tiramisu/api.py:1551
msgid "unknown when {} (must be in append or remove)"
msgstr ""
#: tiramisu/api.py:1424 tiramisu/api.py:1448 tiramisu/config.py:1680
#: tiramisu/api.py:1537 tiramisu/api.py:1561 tiramisu/config.py:1691
msgid "unknown type {}"
msgstr ""
#: tiramisu/api.py:1812
#: tiramisu/api.py:1927
msgid "do not use unrestraint, nowarnings or forcepermissive together"
msgstr ""
#: tiramisu/autolib.py:80
#: tiramisu/autolib.py:89
msgid "args in params must be a tuple"
msgstr ""
#: tiramisu/autolib.py:83 tiramisu/autolib.py:88
#: tiramisu/autolib.py:92 tiramisu/autolib.py:97
msgid "arg in params must be a Param"
msgstr ""
#: tiramisu/autolib.py:85
#: tiramisu/autolib.py:94
msgid "kwargs in params must be a dict"
msgstr ""
#: tiramisu/autolib.py:113
#: tiramisu/autolib.py:122
msgid "paramoption needs an option not {}"
msgstr ""
#: tiramisu/autolib.py:119
#: tiramisu/autolib.py:128
msgid "param must have a boolean not a {} for notraisepropertyerror"
msgstr ""
#: tiramisu/autolib.py:122
#: tiramisu/autolib.py:131
msgid "param must have a boolean not a {} for raisepropertyerror"
msgstr ""
#: tiramisu/autolib.py:212
#: tiramisu/autolib.py:160
msgid "identifiers in ParamDynOption must be a list, not {0}"
msgstr ""
#: tiramisu/autolib.py:166
msgid "optional in ParamDynOption must be a boolean, not {0}"
msgstr ""
#: tiramisu/autolib.py:220
msgid "cannot add option in information after creating config"
msgstr ""
#: tiramisu/autolib.py:223
msgid "cannot redefine option in information"
msgstr ""
#: tiramisu/autolib.py:227
msgid "option in ParamInformation cannot be a symlinkoption"
msgstr ""
#: tiramisu/autolib.py:215
#: tiramisu/autolib.py:230
msgid "option in ParamInformation cannot be a follower"
msgstr ""
#: tiramisu/autolib.py:218
#: tiramisu/autolib.py:233
msgid "option in ParamInformation cannot be a dynamic option"
msgstr ""
#: tiramisu/autolib.py:279
#: tiramisu/autolib.py:294
msgid "first argument ({0}) must be a function"
msgstr ""
#: tiramisu/autolib.py:283
#: tiramisu/autolib.py:298
msgid "help_function ({0}) must be a function"
msgstr ""
#: tiramisu/autolib.py:452 tiramisu/autolib.py:514
msgid "unable to carry out a calculation for {}, {}"
msgstr ""
#: tiramisu/autolib.py:461 tiramisu/autolib.py:521
#: tiramisu/autolib.py:469 tiramisu/autolib.py:525
msgid "the option {0} is used in a calculation but is invalid ({1})"
msgstr ""
#: tiramisu/autolib.py:477 tiramisu/autolib.py:535 tiramisu/autolib.py:584
#: tiramisu/autolib.py:482 tiramisu/autolib.py:539 tiramisu/autolib.py:589
msgid "unable to get value for calculating {0}, {1}"
msgstr ""
#: tiramisu/autolib.py:601
#: tiramisu/autolib.py:518
msgid "unable to carry out a calculation for {0}, {1}"
msgstr ""
#: tiramisu/autolib.py:564
msgid "cannot find information for {0}, {1} is a dynamic option"
msgstr ""
#: tiramisu/autolib.py:604
msgid "option {0} is not a dynoptiondescription or in a dynoptiondescription"
msgstr ""
#: tiramisu/autolib.py:848
msgid "the \"{}\" function with positional arguments \"{}\" and keyword arguments \"{}\" must not return a list (\"{}\") for the follower option {}"
#: tiramisu/autolib.py:697
msgid "cannot calculate arguments for {0}, {1} with identifier \"{2}\", there is no identifiers"
msgstr ""
#: tiramisu/autolib.py:863
msgid "the \"{}\" function must not return a list (\"{}\") for the follower option {}"
#: tiramisu/autolib.py:709
msgid "cannot calculate arguments for {0}, {1} with identifier \"{2}\", list of valid identifiers: {3}"
msgstr ""
#: tiramisu/autolib.py:904
msgid "unexpected error \"{0}\" in function \"{1}\" with arguments \"{3}\" and \"{4}\" for option {2}"
#: tiramisu/autolib.py:820
msgid "the follower {0} must have index in carry_out_calculation!"
msgstr ""
#: tiramisu/autolib.py:915
msgid "unexpected error \"{0}\" in function \"{1}\" for option {2}"
#: tiramisu/autolib.py:932
msgid "unexpected error \"{1}\" in function \"{2}\" with arguments \"{3}\" and \"{4}\" for option {0}"
msgstr ""
#: tiramisu/config.py:419
msgid "index \"{0}\" is greater than the leadership length \"{1}\" for option {2}"
#: tiramisu/autolib.py:942
msgid "unexpected error \"{1}\" in function \"{2}\" for option {0}"
msgstr ""
#: tiramisu/config.py:579
#: tiramisu/config.py:391 tiramisu/config.py:413
msgid "option name {0} is not unique in {1}"
msgstr ""
#: tiramisu/config.py:638
msgid "there is no option description for this config (may be GroupConfig)"
msgstr ""
#: tiramisu/config.py:668
msgid "no option found in config with these criteria"
msgstr ""
#: tiramisu/config.py:871
msgid "the follower option {0} has greater length ({1}) than the leader length ({2})"
msgstr ""
#: tiramisu/config.py:982 tiramisu/option/optiondescription.py:74
#: tiramisu/config.py:989 tiramisu/option/optiondescription.py:74
msgid "option description seems to be part of an other config"
msgstr ""
#: tiramisu/config.py:1144
#: tiramisu/config.py:1151
msgid "parent of {0} not already exists"
msgstr ""
#: tiramisu/config.py:1191
#: tiramisu/config.py:1198
msgid "cannot set leadership object has root optiondescription"
msgstr ""
#: tiramisu/config.py:1194
#: tiramisu/config.py:1201
msgid "cannot set dynoptiondescription object has root optiondescription"
msgstr ""
#: tiramisu/config.py:1246
#: tiramisu/config.py:1248
msgid "child must be a Config, GroupConfig, MixConfig or MetaConfig"
msgstr ""
#: tiramisu/config.py:1257
msgid "config name must be uniq in groupconfig for \"{0}\""
msgstr ""
#: tiramisu/config.py:1457
#: tiramisu/config.py:1466
msgid "unknown config \"{}\""
msgstr ""
#: tiramisu/config.py:1482
#: tiramisu/config.py:1491
msgid "child must be a Config, MixConfig or MetaConfig"
msgstr ""
#: tiramisu/config.py:1517
#: tiramisu/config.py:1526
msgid "force_default, force_default_if_same or force_dont_change_value cannot be set with only_config"
msgstr ""
#: tiramisu/config.py:1527
#: tiramisu/config.py:1536
msgid "force_default and force_dont_change_value cannot be set together"
msgstr ""
#: tiramisu/config.py:1676
#: tiramisu/config.py:1687
msgid "config name must be uniq in groupconfig for {0}"
msgstr ""
#: tiramisu/config.py:1721
#: tiramisu/config.py:1732
msgid "config added has no name, the name is mandatory"
msgstr ""
#: tiramisu/config.py:1726
#: tiramisu/config.py:1737
msgid "config name \"{0}\" is not uniq in groupconfig \"{1}\""
msgstr ""
#: tiramisu/config.py:1744 tiramisu/config.py:1750
#: tiramisu/config.py:1755 tiramisu/config.py:1761
msgid "cannot find the config {0}"
msgstr ""
#: tiramisu/config.py:1776
#: tiramisu/config.py:1787
msgid "MetaConfig with optiondescription must have string has child, not {}"
msgstr ""
#: tiramisu/config.py:1788
#: tiramisu/config.py:1799
msgid "child must be a Config or MetaConfig"
msgstr ""
#: tiramisu/config.py:1793
#: tiramisu/config.py:1804
msgid "all config in metaconfig must have the same optiondescription"
msgstr ""
#: tiramisu/config.py:1810
#: tiramisu/config.py:1821
msgid "metaconfig must have the same optiondescription"
msgstr ""
#: tiramisu/error.py:31
#: tiramisu/error.py:48
msgid "and"
msgstr ""
#: tiramisu/error.py:33
#: tiramisu/error.py:50
msgid "or"
msgstr ""
#: tiramisu/error.py:55
#: tiramisu/error.py:72
msgid " {} "
msgstr ""
#: tiramisu/error.py:108
msgid "property"
#: tiramisu/error.py:145
msgid "cannot modify the {0} {1} at index \"{2}\" because {3} is frozen"
msgstr ""
#: tiramisu/error.py:110
msgid "properties"
#: tiramisu/error.py:149
msgid "cannot modify the {0} {1} at index \"{2}\" because is frozen"
msgstr ""
#: tiramisu/error.py:113
msgid "cannot modify the {0} {1} because \"{2}\" has {3} {4}"
#: tiramisu/error.py:154
msgid "cannot modify the {0} {1} because {2} is frozen"
msgstr ""
#: tiramisu/error.py:115
msgid "cannot modify the {0} {1} because has {2} {3}"
#: tiramisu/error.py:156
msgid "cannot modify the {0} {1} because is frozen"
msgstr ""
#: tiramisu/error.py:118
msgid "cannot access to {0} {1} because \"{2}\" has {3} {4}"
#: tiramisu/error.py:160
msgid "cannot access to {0} {1} at index \"{2}\" because {3} hasn't value"
msgstr ""
#: tiramisu/error.py:120
#: tiramisu/error.py:164
msgid "{0} {1} at index \"{2}\" is mandatory but hasn't value"
msgstr ""
#: tiramisu/error.py:167
msgid "cannot access to {0} {1} because {2} hasn't value"
msgstr ""
#: tiramisu/error.py:169
msgid "{0} {1} is mandatory but hasn't value"
msgstr ""
#: tiramisu/error.py:173
msgid "cannot access to {0} {1} at index \"{2}\" because {3} has {4} {5}"
msgstr ""
#: tiramisu/error.py:177
msgid "cannot access to {0} {1} at index \"{2}\" because has {3} {4}"
msgstr ""
#: tiramisu/error.py:182
msgid "cannot access to {0} {1} because {2} has {3} {4}"
msgstr ""
#: tiramisu/error.py:184
msgid "cannot access to {0} {1} because has {2} {3}"
msgstr ""
#: tiramisu/error.py:192
#: tiramisu/error.py:187
msgid "property"
msgstr ""
#: tiramisu/error.py:189
msgid "properties"
msgstr ""
#: tiramisu/error.py:204
msgid "cannot access to \"{0}\" it's a dynamic option"
msgstr ""
#: tiramisu/error.py:205
msgid "\"{0}\" is not an option"
msgstr ""
#: tiramisu/error.py:269
msgid "cannot set \"group_type\" attribute for the Leadership {0}"
msgstr ""
#: tiramisu/error.py:273
msgid "the leader {0} cannot have \"{1}\" property"
msgstr ""
#: tiramisu/error.py:277
msgid "the leader {0} cannot have \"force_default_on_freeze\" or \"force_metaconfig_on_freeze\" property without \"frozen\""
msgstr ""
#: tiramisu/error.py:281
msgid "cannot reduce length of the leader {0}"
msgstr ""
#: tiramisu/error.py:283
msgid "index \"{0}\" is greater than the leadership length \"{1}\" for option {2}"
msgstr ""
#: tiramisu/error.py:287
msgid "the follower option {0} has greater length ({1}) than the leader length ({2})"
msgstr ""
#: tiramisu/error.py:292
msgid "the \"{0}\" function with positional arguments \"{1}\" and keyword arguments \"{2}\" must not return a list (\"{3}\") for the follower option {4}"
msgstr ""
#: tiramisu/error.py:297
msgid "the \"{0}\" function must not return a list (\"{1}\") for the follower option {2}"
msgstr ""
#: tiramisu/error.py:333
msgid "invalid value"
msgstr ""
#: tiramisu/error.py:201
msgid "attention, \"{0}\" could be an invalid {1} for \"{2}\""
#: tiramisu/error.py:343
msgid "attention, \"{0}\" could be an invalid {1} for {2}"
msgstr ""
#: tiramisu/error.py:219 tiramisu/error.py:228
msgid "\"{0}\" is an invalid {1} for \"{2}\""
#: tiramisu/error.py:347
msgid "attention, \"{0}\" could be an invalid {1} for {2} at index \"{3}\""
msgstr ""
#: tiramisu/error.py:368 tiramisu/error.py:379
msgid "\"{0}\" is an invalid {1} for {2}"
msgstr ""
#: tiramisu/error.py:370
msgid "\"{0}\" is an invalid {1} for {2} at index \"{3}\""
msgstr ""
#: tiramisu/function.py:65
@ -375,31 +471,31 @@ msgstr ""
msgid "the value of \"{0}\" is not {1}"
msgstr ""
#: tiramisu/option/baseoption.py:75 tiramisu/option/symlinkoption.py:44
#: tiramisu/option/baseoption.py:76 tiramisu/option/symlinkoption.py:44
msgid "\"{0}\" is an invalid name for an option"
msgstr ""
#: tiramisu/option/baseoption.py:88
#: tiramisu/option/baseoption.py:89
msgid "invalid properties type {0} for {1}, must be a frozenset"
msgstr ""
#: tiramisu/option/baseoption.py:98
#: tiramisu/option/baseoption.py:102
msgid "invalid property type {0} for {1}, must be a string or a Calculation"
msgstr ""
#: tiramisu/option/baseoption.py:249
#: tiramisu/option/baseoption.py:253
msgid "information's item for {0} not found: \"{1}\""
msgstr ""
#: tiramisu/option/baseoption.py:267
#: tiramisu/option/baseoption.py:271
msgid "'{0}' ({1}) object attribute '{2}' is read-only"
msgstr ""
#: tiramisu/option/baseoption.py:308
#: tiramisu/option/baseoption.py:312
msgid "\"{}\" ({}) object attribute \"{}\" is read-only"
msgstr ""
#: tiramisu/option/baseoption.py:320
#: tiramisu/option/baseoption.py:324
msgid "{0} not part of any Config"
msgstr ""
@ -407,80 +503,96 @@ msgstr ""
msgid "invalid string"
msgstr ""
#: tiramisu/option/choiceoption.py:47
#: tiramisu/option/choiceoption.py:52
msgid "values must be a tuple or a calculation for {0}"
msgstr ""
#: tiramisu/option/choiceoption.py:70
#: tiramisu/option/choiceoption.py:75
msgid "the calculated values \"{0}\" for \"{1}\" is not a list"
msgstr ""
#: tiramisu/option/choiceoption.py:101
#: tiramisu/option/choiceoption.py:106
msgid "only \"{0}\" is allowed"
msgstr ""
#: tiramisu/option/choiceoption.py:103
#: tiramisu/option/choiceoption.py:108
msgid "only {0} are allowed"
msgstr ""
#: tiramisu/option/domainnameoption.py:60
#: tiramisu/option/domainnameoption.py:67
msgid "unknown type {0} for hostname"
msgstr ""
#: tiramisu/option/domainnameoption.py:63
#: tiramisu/option/domainnameoption.py:70
msgid "allow_ip must be a boolean"
msgstr ""
#: tiramisu/option/domainnameoption.py:65
#: tiramisu/option/domainnameoption.py:72
msgid "allow_cidr_network must be a boolean"
msgstr ""
#: tiramisu/option/domainnameoption.py:67
#: tiramisu/option/domainnameoption.py:74
msgid "allow_without_dot must be a boolean"
msgstr ""
#: tiramisu/option/domainnameoption.py:69
#: tiramisu/option/domainnameoption.py:76
msgid "allow_startswith_dot must be a boolean"
msgstr ""
#: tiramisu/option/domainnameoption.py:81
#: tiramisu/option/domainnameoption.py:87
msgid "must start with lowercase characters followed by lowercase characters, number, \"-\" and \".\" characters are allowed"
msgstr ""
#: tiramisu/option/domainnameoption.py:84
#: tiramisu/option/domainnameoption.py:90
msgid "must start with lowercase characters followed by lowercase characters, number, \"-\" and \".\" characters are recommanded"
msgstr ""
#: tiramisu/option/domainnameoption.py:88
#: tiramisu/option/domainnameoption.py:89
#: tiramisu/option/domainnameoption.py:95
msgid "must start with lowercase characters followed by lowercase characters, number and \"-\" characters are allowed"
msgstr ""
#: tiramisu/option/domainnameoption.py:98
msgid "must start with lowercase characters followed by lowercase characters, number and \"-\" characters are recommanded"
msgstr ""
#: tiramisu/option/domainnameoption.py:102
#: tiramisu/option/domainnameoption.py:103
msgid "could be a IP, otherwise {}"
msgstr ""
#: tiramisu/option/domainnameoption.py:134
#: tiramisu/option/domainnameoption.py:148
msgid "invalid length (min 1)"
msgstr ""
#: tiramisu/option/domainnameoption.py:137
#: tiramisu/option/domainnameoption.py:151
msgid "invalid length (max {0})"
msgstr ""
#: tiramisu/option/domainnameoption.py:143
#: tiramisu/option/domainnameoption.py:157
msgid "must have dot"
msgstr ""
#: tiramisu/option/domainnameoption.py:145
#: tiramisu/option/domainnameoption.py:159
msgid "invalid length (max 255)"
msgstr ""
#: tiramisu/option/domainnameoption.py:163
#: tiramisu/option/domainnameoption.py:179
msgid "DNS resolution failed"
msgstr ""
#: tiramisu/option/domainnameoption.py:182
msgid "error resolving DNS: {1}"
msgstr ""
#: tiramisu/option/domainnameoption.py:189
msgid "must not be an IP"
msgstr ""
#: tiramisu/option/domainnameoption.py:186
#: tiramisu/option/domainnameoption.py:212
msgid "some characters are uppercase"
msgstr ""
#: tiramisu/option/dynoptiondescription.py:131
#: tiramisu/option/dynoptiondescription.py:130
msgid "DynOptionDescription identifiers for option {0}, is not a list ({1})"
msgstr ""
@ -488,7 +600,7 @@ msgstr ""
msgid "invalid identifier \"{}\" for option {}"
msgstr ""
#: tiramisu/option/dynoptiondescription.py:150
#: tiramisu/option/dynoptiondescription.py:154
msgid "DynOptionDescription \"{0}\" identifiers return a list with same values \"{1}\""
msgstr ""
@ -501,7 +613,19 @@ msgid "must starts with \"/\""
msgstr ""
#: tiramisu/option/filenameoption.py:78
msgid "cannot find {0} \"{1}\""
msgid "directory"
msgstr ""
#: tiramisu/option/filenameoption.py:78
msgid "file"
msgstr ""
#: tiramisu/option/filenameoption.py:82
msgid "cannot find this {0}"
msgstr ""
#: tiramisu/option/intoption.py:46
msgid "which is not an integer"
msgstr ""
#: tiramisu/option/intoption.py:52
@ -548,31 +672,23 @@ msgstr ""
msgid "must be private IP"
msgstr ""
#: tiramisu/option/leadership.py:55
msgid "cannot set \"group_type\" attribute for a Leadership"
msgstr ""
#: tiramisu/option/leadership.py:67
#: tiramisu/option/leadership.py:65
msgid "a leader and a follower are mandatories in leadership \"{}\""
msgstr ""
#: tiramisu/option/leadership.py:89
msgid "leader cannot have \"{}\" property"
msgstr ""
#: tiramisu/option/leadership.py:101
#: tiramisu/option/leadership.py:97
msgid "leadership {0} shall not have a symlinkoption"
msgstr ""
#: tiramisu/option/leadership.py:108
#: tiramisu/option/leadership.py:104
msgid "leadership {0} shall not have a subgroup"
msgstr ""
#: tiramisu/option/leadership.py:114
msgid "only multi option allowed in leadership {0} but option {1} is not a multi"
#: tiramisu/option/leadership.py:110
msgid "only multi option are allowed in leadership {0} but option {1} is not a multi"
msgstr ""
#: tiramisu/option/leadership.py:141
#: tiramisu/option/leadership.py:137
msgid "not allowed default value for follower option {0} in leadership {1}"
msgstr ""
@ -604,67 +720,61 @@ msgstr ""
msgid "validators must be a Calculation for \"{0}\""
msgstr ""
#: tiramisu/option/option.py:146
#: tiramisu/option/option.py:141
msgid "invalid default_multi value \"{0}\" for option {1}"
msgstr ""
#: tiramisu/option/option.py:154
#: tiramisu/option/option.py:149
msgid "invalid default_multi value \"{0}\" for option {1}, {2}"
msgstr ""
#: tiramisu/option/option.py:167
#: tiramisu/option/option.py:162
msgid "invalid default_multi value \"{0}\" for option {1}, must be a list for a submulti"
msgstr ""
#: tiramisu/option/option.py:290
#: tiramisu/option/option.py:294
msgid "the value \"{}\" is not unique"
msgstr ""
#: tiramisu/option/option.py:352
#: tiramisu/option/option.py:357
msgid "which must not be a list"
msgstr ""
#: tiramisu/option/option.py:404 tiramisu/option/option.py:430
#: tiramisu/option/option.py:412 tiramisu/option/option.py:450
msgid "which must be a list"
msgstr ""
#: tiramisu/option/option.py:424
#: tiramisu/option/option.py:440
msgid "which \"{}\" must be a list of list"
msgstr ""
#: tiramisu/option/optiondescription.py:109
msgid "duplicate option: {0}"
#: tiramisu/option/optiondescription.py:111
#: tiramisu/option/optiondescription.py:117
msgid "\"{0}\" option description"
msgstr ""
#: tiramisu/option/optiondescription.py:244
msgid "unknown option \"{0}\" in root optiondescription (it's a dynamic option)"
#: tiramisu/option/optiondescription.py:115
#: tiramisu/option/optiondescription.py:121
msgid "root option description"
msgstr ""
#: tiramisu/option/optiondescription.py:279
msgid "unknown option \"{0}\" in root optiondescription"
#: tiramisu/option/optiondescription.py:123
msgid "option \"{0}\" is include in {1} but is also in {2}"
msgstr ""
#: tiramisu/option/optiondescription.py:282
msgid "unknown option \"{0}\" in optiondescription {1}"
msgstr ""
#: tiramisu/option/optiondescription.py:338
#: tiramisu/option/optiondescription.py:328
msgid "children in optiondescription \"{}\" must be a list"
msgstr ""
#: tiramisu/option/optiondescription.py:366
msgid "duplicate option name: \"{0}\""
#: tiramisu/option/optiondescription.py:357
msgid "the option name \"{0}\" is duplicate in \"{1}\""
msgstr ""
#: tiramisu/option/optiondescription.py:372
msgid "the option's name \"{0}\" start as the dynoptiondescription's name \"{1}\""
msgstr ""
#: tiramisu/option/optiondescription.py:415
#: tiramisu/option/optiondescription.py:411
msgid "cannot change group_type if already set (old {0}, new {1})"
msgstr ""
#: tiramisu/option/optiondescription.py:420
#: tiramisu/option/optiondescription.py:416
msgid "group_type: {0} not allowed"
msgstr ""
@ -705,79 +815,79 @@ msgstr ""
msgid "too weak"
msgstr ""
#: tiramisu/option/portoption.py:74
#: tiramisu/option/portoption.py:77
msgid "inconsistency in allowed range"
msgstr ""
#: tiramisu/option/portoption.py:79
#: tiramisu/option/portoption.py:82
msgid "max value is empty"
msgstr ""
#: tiramisu/option/portoption.py:92
#: tiramisu/option/portoption.py:95
msgid "range must have two values only"
msgstr ""
#: tiramisu/option/portoption.py:95
#: tiramisu/option/portoption.py:98
msgid "first port in range must be smaller than the second one"
msgstr ""
#: tiramisu/option/portoption.py:121
#: tiramisu/option/portoption.py:124
msgid "should be between {0} and {1}"
msgstr ""
#: tiramisu/option/portoption.py:123
#: tiramisu/option/portoption.py:126
msgid "must be between {0} and {1}"
msgstr ""
#: tiramisu/option/stroption.py:41
msgid "which is not a string"
msgstr ""
#: tiramisu/option/symlinkoption.py:51
msgid "malformed symlink second parameters must be an option for \"{0}\", not {1}"
msgstr ""
#: tiramisu/option/urloption.py:91
#: tiramisu/option/urloption.py:92
msgid "must start with http:// or https://"
msgstr ""
#: tiramisu/option/urloption.py:119
#: tiramisu/option/urloption.py:117
msgid "the port \"{0}\" is invalid: {1}"
msgstr ""
#: tiramisu/option/urloption.py:124
msgid "the domain \"{0}\" is invalid: {1}"
msgstr ""
#: tiramisu/option/urloption.py:128
msgid "must ends with a valid resource name"
msgstr ""
#: tiramisu/setting.py:255
#: tiramisu/setting.py:258
msgid "can't rebind {0}"
msgstr ""
#: tiramisu/setting.py:262
#: tiramisu/setting.py:265
msgid "can't unbind {0}"
msgstr ""
#: tiramisu/setting.py:464
#: tiramisu/setting.py:467
msgid "invalid property type {type(new_prop)} for {subconfig.option.impl_getname()} with {prop.function.__name__} function"
msgstr ""
#: tiramisu/setting.py:476
msgid "leader cannot have \"{new_prop}\" property"
msgstr ""
#: tiramisu/setting.py:564
msgid "leader cannot have \"{0}\" property"
msgstr ""
#: tiramisu/setting.py:573
msgid "a leader ({0}) cannot have \"force_default_on_freeze\" or \"force_metaconfig_on_freeze\" property without \"frozen\""
msgstr ""
#: tiramisu/setting.py:607
#: tiramisu/setting.py:606
msgid "permissive must be a frozenset"
msgstr ""
#: tiramisu/setting.py:617
#: tiramisu/setting.py:616
msgid "cannot add those permissives: {0}"
msgstr ""
#: tiramisu/setting.py:654
#: tiramisu/setting.py:653
msgid "can't reset properties to the symlinkoption \"{}\""
msgstr ""
#: tiramisu/setting.py:667
#: tiramisu/setting.py:666
msgid "can't reset permissives to the symlinkoption \"{}\""
msgstr ""
@ -805,19 +915,15 @@ msgstr ""
msgid "unknown action {}"
msgstr ""
#: tiramisu/value.py:564 tiramisu/value.py:861
#: tiramisu/value.py:557 tiramisu/value.py:859
msgid "set owner \"{0}\" is forbidden"
msgstr ""
#: tiramisu/value.py:571
#: tiramisu/value.py:564
msgid "\"{0}\" is a default value, so we cannot change owner to \"{1}\""
msgstr ""
#: tiramisu/value.py:740
msgid "index {index} is greater than the length {length} for option {subconfig.option.impl_get_display_name(with_quote=True)}"
msgstr ""
#: tiramisu/value.py:847
#: tiramisu/value.py:845
msgid "information's item not found \"{}\""
msgstr ""

View file

@ -4,7 +4,7 @@ requires = ["flit_core >=3.8.0,<4"]
[project]
name = "tiramisu"
version = "5.1.0"
version = "5.2.0a11"
authors = [{name = "Emmanuel Garette", email = "gnunux@gnunux.info"}]
readme = "README.md"
description = "an options controller tool"
@ -18,6 +18,8 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: 3",
"Operating System :: OS Independent",
"Natural Language :: English",
@ -33,5 +35,9 @@ name = "cz_conventional_commits"
tag_format = "$version"
version_scheme = "pep440"
version_provider = "pep621"
version_files = [
"tiramisu/__version__.py",
"pyproject.toml:version"
]
#update_changelog_on_bump = true
changelog_merge_prerelease = true

View file

@ -1,5 +1,4 @@
# from json import dumps, loads
import asyncio
from os import environ
try:
from tiramisu_api import Config
@ -63,3 +62,14 @@ def parse_od_get(dico):
else:
ret[k.path()] = v
return ret
def get_dependencies(option):
ret = []
for a in option.dependencies():
if a.isoptiondescription():
ret.append((a.path(), None))
else:
ret.append((a.path(), a.index()))
ret.sort()
return ret

View file

@ -69,11 +69,11 @@ def test_cache_importation_property():
cfg = Config(od1)
cfg.option('u2').property.add('prop')
export = cfg.property.exportation()
assert cfg.option('u2').property.get() == {'prop'}
assert cfg.option('u2').property.get() == {'validator', 'prop'}
cfg.option('u2').property.add('prop2')
assert cfg.option('u2').property.get() == {'prop', 'prop2'}
assert cfg.option('u2').property.get() == {'validator', 'prop', 'prop2'}
cfg.property.importation(export)
assert cfg.option('u2').property.get() == {'prop'}
assert cfg.option('u2').property.get() == {'validator', 'prop'}
cfg = Config(od1)
# assert not list_sessions()
@ -366,8 +366,8 @@ def test_cache_leader_and_followers():
cfg.value.get()
global_props = ['cache', 'disabled', 'frozen', 'hidden', 'validator', 'warnings', 'force_store_value']
val1_props = []
val1_val1_props = ['empty', 'unique']
val1_val2_props = []
val1_val1_props = ['empty', 'unique', 'validator']
val1_val2_props = ['validator']
global_props = frozenset(global_props)
val1_props = frozenset(val1_props)
val1_val1_props = frozenset(val1_val1_props)
@ -384,7 +384,7 @@ def test_cache_leader_and_followers():
#
cfg.option('val1.val1').value.set([None])
val_val2_props = {idx_val2: (val1_val2_props, None), None: (set(), None)}
compare(settings.get_cached(), {'val1.val1': {None: ({'empty', 'unique'}, None, True)}})
compare(settings.get_cached(), {'val1.val1': {None: ({'validator', 'empty', 'unique'}, None, True)}})
compare(values.get_cached(), {'val1.val1': {None: ([None], None, True)}})
cfg.value.get()
#has value
@ -416,8 +416,8 @@ def test_cache_leader_callback():
cfg.value.get()
global_props = ['cache', 'disabled', 'frozen', 'hidden', 'validator', 'warnings', 'force_store_value']
val1_props = []
val1_val1_props = ['empty', 'unique']
val1_val2_props = []
val1_val1_props = ['empty', 'unique', 'validator']
val1_val2_props = ['validator']
global_props = frozenset(global_props)
val1_props = frozenset(val1_props)
val1_val1_props = frozenset(val1_val1_props)
@ -429,7 +429,7 @@ def test_cache_leader_callback():
})
compare(values.get_cached(), {'val1.val1': {None: ([], None)}})
cfg.option('val1.val1').value.set([None])
compare(settings.get_cached(), {'val1.val1': {None: ({'unique', 'empty'}, None, True)}})
compare(settings.get_cached(), {'val1.val1': {None: ({'unique', 'empty', 'validator'}, None, True)}})
compare(values.get_cached(), {'val1.val1': {None: ([None], None, True)}})
cfg.value.get()
@ -451,24 +451,24 @@ def test_cache_requires():
settings = cfg._config_bag.context.properties_cache
assert values.get_cached() == {}
assert cfg.option('ip_address_service').value.get() == None
compare(settings.get_cached(), {'activate_service': {None: (set([]), None)},
'ip_address_service': {None: (set([]), None)}})
compare(settings.get_cached(), {'activate_service': {None: ({'validator'}, None)},
'ip_address_service': {None: ({"validator"}, None)}})
compare(values.get_cached(), {'ip_address_service': {None: (None, None)},
'activate_service': {None: (True, None)}})
cfg.value.get()
compare(settings.get_cached(), {'activate_service': {None: (set([]), None)},
'ip_address_service': {None: (set([]), None)}})
compare(settings.get_cached(), {'activate_service': {None: ({"validator"}, None)},
'ip_address_service': {None: ({"validator"}, None)}})
compare(values.get_cached(), {'ip_address_service': {None: (None, None)},
'activate_service': {None: (True, None)}})
cfg.option('ip_address_service').value.set('1.1.1.1')
compare(settings.get_cached(), {'activate_service': {None: (set([]), None)}})
compare(settings.get_cached(), {'activate_service': {None: ({"validator"}, None)}})
compare(values.get_cached(), {'activate_service': {None: (True, None)}, 'ip_address_service': {None: ('1.1.1.1', None, True)}})
cfg.value.get()
compare(settings.get_cached(), {'activate_service': {None: (set([]), None)},
'ip_address_service': {None: (set([]), None)}})
compare(settings.get_cached(), {'activate_service': {None: ({"validator"}, None)},
'ip_address_service': {None: ({"validator"}, None)}})
compare(values.get_cached(), {'ip_address_service': {None: ('1.1.1.1', None)},
'activate_service': {None: (True, None)}})
@ -477,8 +477,8 @@ def test_cache_requires():
compare(values.get_cached(), {'activate_service': {None: (False, None)}})
cfg.value.get()
compare(settings.get_cached(), {'activate_service': {None: (set([]), None)},
'ip_address_service': {None: (set(['disabled']), None)}})
compare(settings.get_cached(), {'activate_service': {None: ({"validator"}, None)},
'ip_address_service': {None: ({'disabled', "validator"}, None)}})
compare(values.get_cached(), {'activate_service': {None: (False, None)}})
# assert not list_sessions()
@ -499,19 +499,19 @@ def test_cache_global_properties():
settings = cfg._config_bag.context.properties_cache
assert values.get_cached() == {}
assert cfg.option('ip_address_service').value.get() == None
compare(settings.get_cached(), {'activate_service': {None: (set([]), None)},
'ip_address_service': {None: (set([]), None)}})
compare(settings.get_cached(), {'activate_service': {None: ({"validator"}, None)},
'ip_address_service': {None: ({"validator"}, None)}})
compare(values.get_cached(), {'ip_address_service': {None: (None, None)},
'activate_service': {None: (True, None)}})
cfg.property.remove('disabled')
assert cfg.option('ip_address_service').value.get() == None
compare(settings.get_cached(), {'activate_service': {None: (set([]), None)},
'ip_address_service': {None: (set([]), None)}})
compare(settings.get_cached(), {'activate_service': {None: ({"validator"}, None)},
'ip_address_service': {None: ({"validator"}, None)}})
cfg.property.add('test')
assert cfg.option('ip_address_service').value.get() == None
compare(settings.get_cached(), {'activate_service': {None: (set([]), None)},
'ip_address_service': {None: (set([]), None)}})
compare(settings.get_cached(), {'activate_service': {None: ({"validator"}, None)},
'ip_address_service': {None: ({"validator"}, None)}})
# assert not list_sessions()

View file

@ -162,6 +162,10 @@ def test_choiceoption_calc_opt_function(config_type):
cfg = get_config(cfg, config_type)
assert cfg.option('choice').owner.isdefault()
#
dep = cfg.option('str').dependencies()
assert len(dep) == 1
assert dep[0].path() == 'choice'
#
cfg.option('choice').value.set('val1')
assert cfg.option('choice').owner.get() == owner
#
@ -187,7 +191,6 @@ def test_choiceoption_calc_opt_function_propertyerror():
# assert not list_sessions()
#def test_choiceoption_calc_opt_multi_function(config_type):
def test_choiceoption_calc_opt_multi_function():
# FIXME
config_type = 'tiramisu'

View file

@ -332,7 +332,7 @@ def test_duplicated_option():
g1
#in same OptionDescription
with pytest.raises(ConflictError):
d1 = OptionDescription('od', '', [g1, g1])
OptionDescription('od', '', [g1, g1])
# assert not list_sessions()
@ -346,6 +346,28 @@ def test_duplicated_option_diff_od():
Config(d2)
def test_duplicated_option_diff_od_2():
g1 = IntOption('g1', '', 1)
d1 = OptionDescription('od1', '', [g1])
#in different OptionDescription
d2 = OptionDescription('od2', '', [d1, g1])
d2
with pytest.raises(ConflictError):
Config(d2)
def test_duplicated_option_diff_od_3():
g1 = IntOption('g1', '', 1)
d1 = OptionDescription('od1', '', [g1])
d3 = OptionDescription('od3', '', [g1])
#in different OptionDescription
d2 = OptionDescription('od2', '', [d1, d3])
d4 = OptionDescription('od4', '', [d2])
d4
with pytest.raises(ConflictError):
Config(d4)
def test_cannot_assign_value_to_option_description():
od1 = make_description()
cfg = Config(od1)
@ -380,12 +402,12 @@ def test_prefix_error():
try:
cfg.option('test1').value.set('yes')
except Exception as err:
assert str(err) == _('"{0}" is an invalid {1} for "{2}"').format('yes', _('integer'), 'test1')
assert str(err) == _('"{0}" is an invalid {1} for "{2}", which is not an integer').format('yes', _('integer'), 'test1')
try:
cfg.option('test1').value.set('yes')
except Exception as err:
err.prefix = ''
assert str(err) == _('invalid value')
assert str(err) == _('which is not an integer')
# assert not list_sessions()

View file

@ -13,12 +13,6 @@ from tiramisu.error import PropertiesOptionError, ValueWarning, ConfigError
import warnings
#def teardown_function(function):
# # test_od_not_list emit a warnings because of doesn't create a Config
# with warnings.catch_warnings(record=True) as w:
# assert list_sessions() == [], 'session list is not empty when leaving "{}"'.format(function.__name__)
def make_description():
gcoption = ChoiceOption('name', 'GC name', ('ref', 'framework'), 'ref')
gcdummy = BoolOption('dummy', 'dummy', default=False)
@ -352,6 +346,8 @@ def test_invalid_option():
PortOption('a', '', allow_zero=False, allow_wellknown=False, allow_registred=False, allow_private=False)
with raises(ValueError):
PortOption('a', '', 'tcp:80')
with raises(ValueError):
PortOption('a', '', '')
NetworkOption('a', '')
with raises(ValueError):
NetworkOption('a', '', 'string')
@ -423,7 +419,7 @@ def test_config_reset():
cfg.owner.set('test')
assert cfg.owner.get() == 'test'
assert not cfg.option('gc.gc2.bool').value.get()
assert not cfg.option('boolop').property.get()
assert cfg.option('boolop').property.get() == frozenset(["validator"])
assert not cfg.option('boolop').permissive.get()
assert not cfg.option('wantref').information.get('info', None)
#
@ -440,7 +436,7 @@ def test_config_reset():
cfg.config.reset()
assert cfg.owner.get() == 'test'
assert not cfg.option('gc.gc2.bool').value.get()
assert not cfg.option('boolop').property.get()
assert cfg.option('boolop').property.get() == {"validator"}
assert not cfg.option('float').permissive.get()
assert not cfg.option('wantref').information.get('info', None)
# assert not list_sessions()

View file

@ -275,3 +275,9 @@ def test_url(config_type):
with pytest.raises(ValueError):
cfg.option('u').value.set('https://FOO.COM:8443')
# assert not list_sessions()
def test_domainname_existence():
DomainnameOption('d', '', 'google.fr', test_existence=True)
with pytest.raises(ValueError):
DomainnameOption('d', '', 'ljijouuuehyfr.com', test_existence=True)

View file

@ -80,15 +80,15 @@ def test_copy_force_store_value():
assert conf.value.exportation() == {'creole.general.wantref': {None: [True, 'user']}}
assert conf2.value.exportation() == {'creole.general.wantref': {None: [False, 'forced']}}
# assert not list_sessions()
#
#
#def test_copy_force_store_value_metaconfig():
# od1 = make_description()
# meta = MetaConfig([], optiondescription=od1)
# conf = meta.config.new()
# assert meta.property.get() == conf.property.get()
# assert meta.permissive.get() == conf.permissive.get()
# conf.property.read_write()
# assert conf.value.exportation() == {'creole.general.wantref': {None: [False, 'forced']}}
# assert meta.value.exportation() == {}
## assert not list_sessions()
def test_copy_force_store_value_metaconfig():
od1 = make_description()
meta = MetaConfig([], optiondescription=od1)
conf = meta.config.new()
assert meta.property.get() == conf.property.get()
assert meta.permissive.get() == conf.permissive.get()
conf.property.read_write()
assert conf.value.exportation() == {'creole.general.wantref': {None: [False, 'forced']}}
assert meta.value.exportation() == {}
# assert not list_sessions()

View file

@ -1,7 +1,7 @@
# coding: utf-8
from .autopath import do_autopath
do_autopath()
from .config import parse_od_get
from .config import parse_od_get, get_dependencies
import pytest
from tiramisu.setting import groups, owners
@ -300,20 +300,20 @@ def test_prop_dyndescription():
od = OptionDescription('od', '', [dod])
od2 = OptionDescription('od', '', [od])
cfg = Config(od2)
assert set(cfg.option('od.dodval1.st').property.get()) == set(['test'])
assert set(cfg.option('od.dodval2.st').property.get()) == set(['test'])
assert set(cfg.option('od.dodval1.st').property.get()) == {'test', "validator"}
assert set(cfg.option('od.dodval2.st').property.get()) == {'test', "validator"}
cfg.option('od.dodval2.st').property.add('test2')
assert set(cfg.option('od.dodval1.st').property.get()) == set(['test'])
assert set(cfg.option('od.dodval2.st').property.get()) == set(['test', 'test2'])
assert set(cfg.option('od.dodval1.st').property.get()) == {'test', "validator"}
assert set(cfg.option('od.dodval2.st').property.get()) == {'test', 'test2', "validator"}
#
assert set(cfg.option('od.dodval1').property.get()) == set([])
assert set(cfg.option('od.dodval2').property.get()) == set([])
assert set(cfg.option('od.dodval1').property.get()) == set()
assert set(cfg.option('od.dodval2').property.get()) == set()
cfg.option('od.dodval1').property.add('test1')
assert set(cfg.option('od.dodval1').property.get()) == set(['test1'])
assert set(cfg.option('od.dodval2').property.get()) == set([])
assert set(cfg.option('od.dodval1').property.get()) == {'test1'}
assert set(cfg.option('od.dodval2').property.get()) == set()
cfg.option('od.dodval1').property.remove('test1')
assert set(cfg.option('od.dodval1').property.get()) == set([])
assert set(cfg.option('od.dodval2').property.get()) == set([])
assert set(cfg.option('od.dodval1').property.get()) == set()
assert set(cfg.option('od.dodval2').property.get()) == set()
# assert not list_sessions()
@ -336,6 +336,7 @@ def test_prop_dyndescription_force_store_value_calculation_prefix():
od2 = OptionDescription('od', '', [od])
cfg = Config(od2)
cfg.property.read_write()
assert get_dependencies(cfg.option('od.lst')) == [('od.dodval1', None), ('od.dodval2', None)]
assert cfg.option('od.dodval1.st').owner.isdefault() == False
assert cfg.option('od.dodval2.st').owner.isdefault() == False
assert parse_od_get(cfg.value.get()) == {'od.lst': ['val1', 'val2'], 'od.dodval1.st': 'val1', 'od.dodval2.st': 'val2'}
@ -394,6 +395,8 @@ def test_callback_dyndescription_outside1():
od = OptionDescription('od', '', [dod, out])
od2 = OptionDescription('od', '', [od, lst])
cfg = Config(od2)
#
assert get_dependencies(cfg.option('od.dodval1.st')) == [('od.out', None)]
assert parse_od_get(cfg.value.get()) == {'od.dodval1.st': 'val', 'od.dodval2.st': 'val', 'od.out': 'val', 'lst': ['val1', 'val2']}
cfg.option('od.dodval1.st').value.set('val1')
cfg.option('od.dodval2.st').value.set('val2')
@ -417,6 +420,8 @@ def test_callback_dyndescription_outside2():
assert parse_od_get(cfg.value.get()) == {'od.dodval1.st': None, 'od.dodval2.st': None, 'od.out': None, 'lst': ['val1', 'val2']}
cfg.option('od.out').value.set('val1')
assert parse_od_get(cfg.value.get()) == {'od.dodval1.st': 'val1', 'od.dodval2.st': 'val1', 'od.out': 'val1', 'lst': ['val1', 'val2']}
#
assert get_dependencies(cfg.option('od.out')) == [('od.dodval1.st', None), ('od.dodval2.st', None)]
# assert not list_sessions()
@ -432,6 +437,20 @@ def test_callback_dyndescription_outside3():
assert parse_od_get(cfg.value.get()) == {'od.out': 'val1', 'lst': ['val1', 'val2']}
def test_callback_dyndescription_outside_optional():
lst = StrOption('lst', '', ['val'], multi=True)
st = StrOption('st', '', 'val')
dod = DynOptionDescription('dod', '', [st], identifiers=Calculation(return_list, Params(ParamOption(lst))))
out = StrOption('out', '', Calculation(calc_value, Params(ParamDynOption(st, ['unknown_val'], optional=True))))
od = OptionDescription('od', '', [dod, out])
od2 = OptionDescription('od', '', [od, lst])
cfg = Config(od2)
assert parse_od_get(cfg.value.get()) == {'od.dodval.st': 'val', 'od.out': None, 'lst': ['val']}
cfg.option('lst').value.set(['val', 'unknown_val'])
assert parse_od_get(cfg.value.get()) == {'od.dodval.st': 'val', 'od.dodunknown_val.st': 'val', 'od.out': 'val', 'lst': ['val', 'unknown_val']}
# assert not list_sessions()
def test_callback_dyndescription_subdyn():
lst = StrOption('lst', '', ['val1', 'val2'], multi=True)
st = StrOption('st', '', 'val1')
@ -602,14 +621,14 @@ def test_prop_dyndescription_context():
od = OptionDescription('od', '', [dod, val1])
od2 = OptionDescription('od', '', [od])
cfg = Config(od2)
assert set(cfg.option('od.dodval1.st').property.get()) == set(['test'])
assert set(cfg.option('od.dodval2.st').property.get()) == set(['test'])
assert set(cfg.option('od.dodval1.st').property.get()) == {"validator", 'test'}
assert set(cfg.option('od.dodval2.st').property.get()) == {"validator", 'test'}
cfg.option('od.dodval2.st').property.add('test2')
assert set(cfg.option('od.dodval1.st').property.get()) == set(['test'])
assert set(cfg.option('od.dodval2.st').property.get()) == set(['test', 'test2'])
assert set(cfg.option('od.dodval1.st').property.get()) == {"validator", 'test'}
assert set(cfg.option('od.dodval2.st').property.get()) == {"validator", 'test', 'test2'}
cfg.option('od.dodval1.st').permissive.add('test')
assert set(cfg.option('od.dodval1.st').property.get()) == set([])
assert set(cfg.option('od.dodval2.st').property.get()) == set(['test', 'test2'])
assert set(cfg.option('od.dodval1.st').property.get()) == {"validator"}
assert set(cfg.option('od.dodval2.st').property.get()) == {"validator", 'test', 'test2'}
# assert not list_sessions()
@ -1177,6 +1196,8 @@ def test_leadership_dyndescription():
cfg = Config(od1)
owner = cfg.owner.get()
#
assert get_dependencies(cfg.option('od.stval1.st1.st2')) == [('od.stval1.st1', None)]
assert get_dependencies(cfg.option('od.stval2.st1.st2')) == [('od.stval2.st1', None)]
assert parse_od_get(cfg.value.get()) == {'od.stval2.st1.st1': [], 'od.stval1.st1.st1': []}
assert cfg.option('od.stval1.st1.st1').value.get() == []
assert cfg.option('od.stval2.st1.st1').value.get() == []
@ -1236,6 +1257,9 @@ def test_leadership_dyndescription_force_store_value_leader():
od1 = OptionDescription('od', '', [od])
cfg = Config(od1)
cfg.property.read_write()
#
assert get_dependencies(cfg.option('od.stval1.st1.st2')) == [('od.stval1.st1', None)]
assert get_dependencies(cfg.option('od.stval2.st1.st2')) == [('od.stval2.st1', None)]
assert cfg.option('od.stval1.st1.st1').owner.isdefault() == False
assert cfg.option('od.stval2.st1.st1').owner.isdefault() == False
assert cfg.option('od.stval1.st1.st2', 0).owner.isdefault() == True
@ -1696,16 +1720,6 @@ def test_leadership_callback_samegroup_dyndescription():
# assert not list_sessions()
def test_invalid_conflict_dyndescription():
st = StrOption('st', '')
dod = DynOptionDescription('dod', '', [st], identifiers=Calculation(return_list))
dodinvalid = StrOption('dodinvalid', '')
dod, dodinvalid
with pytest.raises(ConflictError):
OptionDescription('od', '', [dod, dodinvalid])
# assert not list_sessions()
def test_leadership_default_multi_dyndescription4():
st1 = StrOption('st1', "", multi=True)
st2 = StrOption('st2', "", multi=True, default_multi='no')
@ -2859,3 +2873,86 @@ def test_callback_list_dyndescription_information_not_list():
cfg.information.set('identifier', 'ival3')
assert cfg.option('od.dodival3.st').value.get() == ['ival3', 'val2']
# assert not list_sessions()
def test_dynoption_not_duplicate():
st = StrOption('st', '')
st2 = StrOption('st', '')
dod = DynOptionDescription('od_', '', [st], identifiers=["val1"])
dod2 = DynOptionDescription('od_', '', [st2], identifiers=["val2"])
od = OptionDescription('od', '', [dod, dod2])
od2 = OptionDescription('od', '', [od])
cfg = Config(od2)
assert parse_od_get(cfg.value.get()) == {
'od.od_val1.st': None,
'od.od_val2.st': None,
}
def test_dynoption_duplicate_1():
st = StrOption('st', '', "val1")
od = OptionDescription('od_val', '', [st])
st2 = StrOption('st', '', "val2")
dod = DynOptionDescription('od_', '', [st2], identifiers=["val"])
od1 = OptionDescription('od', '', [od, dod])
od2 = OptionDescription('od', '', [od1])
cfg = Config(od2)
with pytest.raises(ConflictError):
cfg.value.get()
with pytest.raises(ConflictError):
cfg.option('od.od_val').value.get()
def test_dynoption_duplicate_2():
st2 = StrOption('st', '', "val2")
dod = DynOptionDescription('od_', '', [st2], identifiers=["val"])
st = StrOption('st', '', "val1")
od = OptionDescription('od_val', '', [st])
od1 = OptionDescription('od', '', [dod, od])
od2 = OptionDescription('od', '', [od1])
cfg = Config(od2)
with pytest.raises(ConflictError):
cfg.value.get()
with pytest.raises(ConflictError):
cfg.option('od.od_val').value.get()
def test_dynoption_duplicate_3():
st = StrOption('od_val', '')
st2 = StrOption('st', '', "val2")
dod = DynOptionDescription('od_', '', [st2], identifiers=["val"])
od1 = OptionDescription('od', '', [dod, st])
od2 = OptionDescription('od', '', [od1])
cfg = Config(od2)
with pytest.raises(ConflictError):
cfg.value.get()
with pytest.raises(ConflictError):
cfg.option('od.od_val').value.get()
def test_dynoption_duplicate_4():
st = StrOption('st', '')
st2 = StrOption('st', '')
dod = DynOptionDescription('od_', '', [st], identifiers=["val"])
dod2 = DynOptionDescription('od_', '', [st2], identifiers=["val"])
od = OptionDescription('od', '', [dod, dod2])
od2 = OptionDescription('od', '', [od])
cfg = Config(od2)
with pytest.raises(ConflictError):
cfg.value.get()
with pytest.raises(ConflictError):
cfg.option('od.od_val').value.get()
def test_dynoption_duplicate_5():
st = StrOption('st', '')
st2 = StrOption('st', '')
dod = DynOptionDescription('od_', '', [st], identifiers=["val"])
dod2 = DynOptionDescription('od', '', [st2], identifiers=["_val"])
od = OptionDescription('od', '', [dod, dod2])
od2 = OptionDescription('od', '', [od])
cfg = Config(od2)
with pytest.raises(ConflictError):
cfg.value.get()
with pytest.raises(ConflictError):
cfg.option('od.od_val').value.get()

View file

@ -6,7 +6,7 @@ import pytest
from tiramisu.setting import groups, owners
from tiramisu import ChoiceOption, BoolOption, IntOption, IPOption, NetworkOption, NetmaskOption, \
StrOption, OptionDescription, Leadership, Config, Calculation, ParamValue, calc_value, Params
StrOption, OptionDescription, Leadership, Config, Calculation, ParamValue, ParamOption, calc_value, Params, submulti
from tiramisu.error import LeadershipError, PropertiesOptionError, ConfigError
@ -788,6 +788,19 @@ def test_values_with_leader_and_followers_leader_pop():
# assert not list_sessions()
def test_values_with_leader_and_followers_leader_pop_default_value():
ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", default=["192.168.230.145", "192.168.230.146"], multi=True, properties=('notunique',))
netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", default_multi="255.255.255.0", multi=True)
interface1 = Leadership('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
od1 = OptionDescription('toto', '', [interface1])
cfg = Config(od1)
cfg.property.read_write()
cfg.option('ip_admin_eth0.ip_admin_eth0').value.pop(0)
compare(cfg.value.exportation(), {'ip_admin_eth0.ip_admin_eth0': {None: [['192.168.230.146'], 'user']}})
assert cfg.option('ip_admin_eth0.ip_admin_eth0').value.get() == ["192.168.230.146"]
assert cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get() == '255.255.255.0'
def test_follower_unique():
ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True, properties=('unique',))
@ -796,7 +809,7 @@ def test_follower_unique():
cfg = Config(od1)
cfg.option('ip_admin_eth0.ip_admin_eth0').value.set(["192.168.230.145", "192.168.230.146"])
# unique property is removed for a follower
assert not cfg.option('ip_admin_eth0.netmask_admin_eth0', 1).property.get()
assert cfg.option('ip_admin_eth0.netmask_admin_eth0', 1).property.get() == {"validator"}
# assert not list_sessions()
@ -1042,7 +1055,7 @@ def test_follower_force_store_value_reset():
# assert not list_sessions()
#def test_follower_properties():
def test_follower_properties():
ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True, properties=('aproperty',))
interface1 = Leadership('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
@ -1093,3 +1106,25 @@ def test_leader_forbidden_properties_callback(config_type):
cfg = Config(od1)
with pytest.raises(LeadershipError):
cfg.option('ip_admin_eth0.ip_admin_eth0').value.get()
def test_follower_value_not_list():
ip_admin_eth0 = IPOption('ip_admin_eth0', "ip réseau autorisé", multi=True, default=['1.1.1.1'])
netmask_admin_eth0 = NetmaskOption('netmask_admin_eth0', "masque du sous-réseau", default_multi='255.255.255.0', multi=True, properties=('force_store_value',))
interface0 = Leadership('interface0', '', [ip_admin_eth0, netmask_admin_eth0])
od1 = OptionDescription('od', '', [interface0])
od2 = OptionDescription('root', '', [od1])
cfg = Config(od2)
cfg.property.read_write()
with pytest.raises(ValueError):
cfg.option('od.interface0.ip_admin_eth0').value.set(None)
def test_default_calc():
var1 = StrOption('var1', "", multi=True, default=['leader1', 'leader2'], properties=frozenset({"mandatory",}))
var2 = StrOption('var2', "", default_multi=[Calculation(calc_value, Params((ParamOption(var1))))], multi=submulti, properties=frozenset({"mandatory",}))
leader = Leadership('interface0', '', [var1, var2])
od1 = OptionDescription('od', '', [leader])
od2 = OptionDescription('root', '', [od1])
cfg = Config(od2)
assert parse_od_get(cfg.value.get()) == {'od.interface0.var1': [{'od.interface0.var1': 'leader1', 'od.interface0.var2': ['leader1']}, {'od.interface0.var1': 'leader2', 'od.interface0.var2': ['leader2']}]}

View file

@ -13,8 +13,6 @@ from tiramisu.error import PropertiesOptionError, ConfigError
from tiramisu.setting import groups
#def teardown_function(function):
# assert list_sessions() == [], 'session list is not empty when leaving "{}"'.format(function.__name__)
def is_mandatory(variable):
return True
@ -688,25 +686,25 @@ def return_list(val=None, identifier=None):
return ['val1', 'val2']
#def test_mandatory_dyndescription():
# st = StrOption('st', '', properties=('mandatory',))
# dod = DynOptionDescription('dod', '', [st], identifiers=Calculation(return_list))
# od = OptionDescription('od', '', [dod])
# od2 = OptionDescription('od', '', [od])
# cfg = Config(od2)
# cfg.property.read_only()
# compare(cfg.value.mandatory(), ['od.dodval1.st', 'od.dodval2.st'])
#
#
#def test_mandatory_dyndescription_context():
# val1 = StrOption('val1', '', ['val1', 'val2'], multi=True)
# st = StrOption('st', '', properties=('mandatory',))
# dod = DynOptionDescription('dod', '', [st], identifiers=Calculation(return_list, Params(ParamOption(val1))))
# od = OptionDescription('od', '', [dod, val1])
# od2 = OptionDescription('od', '', [od])
# cfg = Config(od2)
# cfg.property.read_only()
# compare(cfg.value.mandatory(), ['od.dodval1.st', 'od.dodval2.st'])
def test_mandatory_dyndescription():
st = StrOption('st', '', properties=('mandatory',))
dod = DynOptionDescription('dod', '', [st], identifiers=Calculation(return_list))
od = OptionDescription('od', '', [dod])
od2 = OptionDescription('od', '', [od])
cfg = Config(od2)
cfg.property.read_only()
compare(cfg.value.mandatory(), ['od.dodval1.st', 'od.dodval2.st'])
def test_mandatory_dyndescription_context():
val1 = StrOption('val1', '', ['val1', 'val2'], multi=True)
st = StrOption('st', '', properties=('mandatory',))
dod = DynOptionDescription('dod', '', [st], identifiers=Calculation(return_list, Params(ParamOption(val1))))
od = OptionDescription('od', '', [dod, val1])
od2 = OptionDescription('od', '', [od])
cfg = Config(od2)
cfg.property.read_only()
compare(cfg.value.mandatory(), ['od.dodval1.st', 'od.dodval2.st'])
def test_mandatory_callback_leader_and_followers_leader():

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -15,76 +15,76 @@ def make_metaconfig():
return MetaConfig([], optiondescription=od2, name='metacfg1')
#def test_multi_parents_path():
# """
# metacfg1 (1) ---
# | -- cfg1
# metacfg2 (2) ---
# """
# metacfg1 = make_metaconfig()
# cfg1 = metacfg1.config.new(type='config', name="cfg1")
# metacfg2 = MetaConfig([cfg1], name='metacfg2')
# #
# assert metacfg1.config.path() == 'metacfg1'
# assert metacfg2.config.path() == 'metacfg2'
# assert cfg1.config.path() == 'metacfg2.metacfg1.cfg1'
#
#
#def test_multi_parents_path_same():
# """
# --- metacfg2 (1) ---
# metacfg1 --| | -- cfg1
# --- metacfg3 (2) ---
# """
# metacfg1 = make_metaconfig()
# metacfg2 = metacfg1.config.new(type='metaconfig', name="metacfg2")
# metacfg3 = metacfg1.config.new(type='metaconfig', name="metacfg3")
# cfg1 = metacfg2.config.new(type='config', name="cfg1")
# metacfg3.config.add(cfg1)
# #
# assert metacfg2.config.path() == 'metacfg1.metacfg2'
# assert metacfg3.config.path() == 'metacfg1.metacfg3'
# assert cfg1.config.path() == 'metacfg1.metacfg3.metacfg1.metacfg2.cfg1'
# metacfg1.option('od1.i1').value.set(1)
# metacfg3.option('od1.i1').value.set(2)
# assert cfg1.option('od1.i1').value.get() == 1
# orideep = cfg1.config.deepcopy(metaconfig_prefix="test_", name='test_cfg1')
# deep = orideep
# while True:
# try:
# children = list(deep.config.list())
# except:
# break
# assert len(children) < 2
# deep = children[0]
# assert deep.config.path() == 'test_metacfg3.test_metacfg1.test_metacfg2.test_cfg1'
# assert cfg1.option('od1.i1').value.get() == 1
#
#
#
#def test_multi_parents_value():
# metacfg1 = make_metaconfig()
# cfg1 = metacfg1.config.new(type='config', name="cfg1")
# metacfg2 = MetaConfig([cfg1], name='metacfg2')
# #
# assert cfg1.option('od1.i1').value.get() == None
# assert cfg1.option('od1.i2').value.get() == 1
# assert cfg1.option('od1.i3').value.get() == None
# #
# assert metacfg1.option('od1.i1').value.get() == None
# assert metacfg1.option('od1.i2').value.get() == 1
# assert metacfg1.option('od1.i3').value.get() == None
# #
# assert metacfg2.option('od1.i1').value.get() == None
# assert metacfg2.option('od1.i2').value.get() == 1
# assert metacfg2.option('od1.i3').value.get() == None
# #
# metacfg1.option('od1.i3').value.set(3)
# assert metacfg1.option('od1.i3').value.get() == 3
# assert cfg1.option('od1.i3').value.get() == 3
# assert metacfg2.option('od1.i2').value.get() == 1
# #
# metacfg2.option('od1.i2').value.set(4)
# assert metacfg2.option('od1.i2').value.get() == 4
# assert metacfg1.option('od1.i2').value.get() == 1
# assert cfg1.option('od1.i2').value.get() == 4
def test_multi_parents_path():
"""
metacfg1 (1) ---
| -- cfg1
metacfg2 (2) ---
"""
metacfg1 = make_metaconfig()
cfg1 = metacfg1.config.new(type='config', name="cfg1")
metacfg2 = MetaConfig([cfg1], name='metacfg2')
#
assert metacfg1.config.path() == 'metacfg1'
assert metacfg2.config.path() == 'metacfg2'
assert cfg1.config.path() == 'metacfg2.metacfg1.cfg1'
def test_multi_parents_path_same():
"""
--- metacfg2 (1) ---
metacfg1 --| | -- cfg1
--- metacfg3 (2) ---
"""
metacfg1 = make_metaconfig()
metacfg2 = metacfg1.config.new(type='metaconfig', name="metacfg2")
metacfg3 = metacfg1.config.new(type='metaconfig', name="metacfg3")
cfg1 = metacfg2.config.new(type='config', name="cfg1")
metacfg3.config.add(cfg1)
#
assert metacfg2.config.path() == 'metacfg1.metacfg2'
assert metacfg3.config.path() == 'metacfg1.metacfg3'
assert cfg1.config.path() == 'metacfg1.metacfg3.metacfg1.metacfg2.cfg1'
metacfg1.option('od1.i1').value.set(1)
metacfg3.option('od1.i1').value.set(2)
assert cfg1.option('od1.i1').value.get() == 1
orideep = cfg1.config.deepcopy(metaconfig_prefix="test_", name='test_cfg1')
deep = orideep
while True:
try:
children = list(deep.config.list())
except:
break
assert len(children) < 2
deep = children[0]
assert deep.config.path() == 'test_metacfg3.test_metacfg1.test_metacfg2.test_cfg1'
assert cfg1.option('od1.i1').value.get() == 1
def test_multi_parents_value():
metacfg1 = make_metaconfig()
cfg1 = metacfg1.config.new(type='config', name="cfg1")
metacfg2 = MetaConfig([cfg1], name='metacfg2')
#
assert cfg1.option('od1.i1').value.get() == None
assert cfg1.option('od1.i2').value.get() == 1
assert cfg1.option('od1.i3').value.get() == None
#
assert metacfg1.option('od1.i1').value.get() == None
assert metacfg1.option('od1.i2').value.get() == 1
assert metacfg1.option('od1.i3').value.get() == None
#
assert metacfg2.option('od1.i1').value.get() == None
assert metacfg2.option('od1.i2').value.get() == 1
assert metacfg2.option('od1.i3').value.get() == None
#
metacfg1.option('od1.i3').value.set(3)
assert metacfg1.option('od1.i3').value.get() == 3
assert cfg1.option('od1.i3').value.get() == 3
assert metacfg2.option('od1.i2').value.get() == 1
#
metacfg2.option('od1.i2').value.set(4)
assert metacfg2.option('od1.i2').value.get() == 4
assert metacfg1.option('od1.i2').value.get() == 1
assert cfg1.option('od1.i2').value.get() == 4

View file

@ -13,7 +13,7 @@ from tiramisu import ChoiceOption, BoolOption, IntOption, FloatOption, \
valid_ip_netmask, ParamSelfOption, ParamInformation, ParamSelfInformation
from tiramisu.error import PropertiesOptionError, ConflictError, LeadershipError, ConfigError
from tiramisu.i18n import _
from .config import config_type, get_config, parse_od_get
from .config import config_type, get_config, parse_od_get, get_dependencies
def return_val():
@ -693,6 +693,7 @@ def test_callback_leader_and_followers_leader(config_type):
cfg = Config(od1)
cfg.property.read_write()
cfg = get_config(cfg, config_type)
assert [(a.path(), a.index()) for a in cfg.option('val1').dependencies()] == [('val2.val2', None)]
assert cfg.option('val1').value.get() == ['val']
assert cfg.option('val2.val2').value.get() == ['val']
#
@ -783,10 +784,29 @@ def test_callback_leader_and_followers_leader2(config_type):
cfg = Config(od1)
cfg.property.read_write()
cfg = get_config(cfg, config_type)
assert get_dependencies(cfg.option('val1.val2')) == [('val1', None)]
assert get_dependencies(cfg.option('val1.val3')) == [('val1', None)] #, ('val2.val2', 0)]
assert get_dependencies(cfg.option('val1.val4')) == [('val1', None)] #, ('val2.val3', 0)]
#
cfg.option('val1.val1').value.set(['val'])
assert get_dependencies(cfg.option('val1.val2')) == [('val1', None), ('val1.val3', 0)]
assert get_dependencies(cfg.option('val1.val2', 0)) == [('val1', None), ('val1.val3', 0)]
assert get_dependencies(cfg.option('val1.val3')) == [('val1', None), ('val1.val4', 0)]
assert get_dependencies(cfg.option('val1.val3', 0)) == [('val1', None), ('val1.val4', 0)]
assert get_dependencies(cfg.option('val1.val4')) == [('val1', None)]
assert cfg.option('val1.val4', 0).value.get() == 'val2'
assert cfg.option('val1.val3', 0).value.get() == 'val2'
assert cfg.option('val1.val2', 0).value.get() == 'val2'
#
cfg.option('val1.val1').value.set(['val1', 'val2'])
assert get_dependencies(cfg.option('val1.val2')) == [('val1', None), ('val1.val3', 0), ('val1.val3', 1)]
assert get_dependencies(cfg.option('val1.val2', 0)) == [('val1', None), ('val1.val3', 0)]
assert get_dependencies(cfg.option('val1.val2', 1)) == [('val1', None), ('val1.val3', 1)]
assert get_dependencies(cfg.option('val1.val3')) == [('val1', None), ('val1.val4', 0), ('val1.val4', 1)]
assert get_dependencies(cfg.option('val1.val3', 0)) == [('val1', None), ('val1.val4', 0)]
assert get_dependencies(cfg.option('val1.val3', 1)) == [('val1', None), ('val1.val4', 1)]
assert get_dependencies(cfg.option('val1.val4')) == [('val1', None)]
# assert not list_sessions()
@ -945,7 +965,7 @@ def test_consistency_leader_and_followers_leader_mandatory_transitive():
try:
cfg.option('val1.val2', 0).value.get()
except PropertiesOptionError as error:
assert str(error) == str(_('cannot access to {0} {1} because has {2} {3}').format('option', '"val2"', _('property'), '"disabled"'))
assert str(error) == str(_('cannot access to {0} {1} at index "{2}" because has {3} {4}').format('option', '"val2"', 0, _('property'), '"disabled"'))
else:
raise Exception('must raises')
assert list(cfg.value.mandatory()) == []
@ -1499,6 +1519,7 @@ def test_leadership_callback_description(config_type):
cfg.option('od.st.st1.st2', 0).value.set('yes')
assert cfg.option('od.st.st1.st1').owner.get() == owner
assert cfg.option('od.st.st1.st2', 0).owner.get() == owner
assert get_dependencies(cfg.option('od.st.st1.st2')) == [('od.st.st1', None)]
# assert not list_sessions()
@ -1515,6 +1536,8 @@ def test_leadership_callback_outside(config_type):
owner = cfg.owner.get()
cfg.option('od.st.st1.st1').value.set(['yes'])
assert parse_od_get(cfg.value.get()) == {'od.st.st1.st1': [{'od.st.st1.st1': 'yes', 'od.st.st1.st2': 'val2'}], 'od.st.st3': ['val2']}
assert get_dependencies(cfg.option('od.st.st1.st2')) == [('od.st.st1', None), ('od.st.st3', None,)]
## assert not list_sessions()

View file

@ -154,8 +154,8 @@ def test_force_default_on_freeze_multi():
# with pytest.raises(ConfigError):
# Config(od1)
# assert not list_sessions()
#
#
#def test_force_metaconfig_on_freeze_leader():
# dummy1 = BoolOption('dummy1', 'Test int option', multi=True, properties=('force_metaconfig_on_freeze',))
# dummy2 = BoolOption('dummy2', 'Test string option', multi=True)

View file

@ -202,10 +202,10 @@ def test_property_get_unique_empty():
od1 = OptionDescription("options", "", [s, s2, s3, s4])
cfg = Config(od1)
cfg.property.read_write()
assert cfg.option('string').property.get() == {'empty', 'unique'}
assert cfg.option('string2').property.get() == {'empty', 'notunique'}
assert cfg.option('string3').property.get() == {'unique', 'notempty'}
assert cfg.option('string4').property.get() == {'notunique', 'notempty'}
assert cfg.option('string').property.get() == {"validator", 'empty', 'unique'}
assert cfg.option('string2').property.get() == {"validator", 'empty', 'notunique'}
assert cfg.option('string3').property.get() == {"validator", 'unique', 'notempty'}
assert cfg.option('string4').property.get() == {"validator", 'notunique', 'notempty'}
# assert not list_sessions()
@ -220,7 +220,7 @@ def test_property_only_raises():
od1 = OptionDescription("options", "", [s, intoption, stroption])
cfg = Config(od1)
cfg.property.read_write()
assert cfg.option('str').property.get() == {'empty', 'unique'}
assert cfg.option('str').property.get() == {'empty', 'unique', 'validator'}
assert cfg.option('str').property.get(only_raises=True) == set()
# assert not list_sessions()
@ -569,23 +569,23 @@ def test_access_by_get_whith_hide():
def test_append_properties():
od1 = make_description()
cfg = Config(od1)
assert cfg.option('gc.dummy').property.get() == set()
assert cfg.option('gc.dummy').property.get() == {"validator"}
cfg.option('gc.dummy').property.add('test')
assert cfg.option('gc.dummy').property.get() == {'test'}
assert cfg.option('gc.dummy').property.get() == {'test', "validator"}
with pytest.raises(ConfigError):
cfg.option('gc.dummy').property.add('force_store_value')
assert cfg.option('gc.dummy').property.get() == {'test'}
assert cfg.option('gc.dummy').property.get() == {'test', "validator"}
# assert not list_sessions()
def test_reset_properties():
od1 = make_description()
cfg = Config(od1)
assert cfg.option('gc.dummy').property.get() == set()
assert cfg.option('gc.dummy').property.get() == {"validator"}
cfg.option('gc.dummy').property.add('frozen')
assert cfg.option('gc.dummy').property.get() == {'frozen'}
assert cfg.option('gc.dummy').property.get() == {"validator", 'frozen'}
cfg.option('gc.dummy').property.reset()
assert cfg.option('gc.dummy').property.get() == set()
assert cfg.option('gc.dummy').property.get() == {"validator"}
# assert not list_sessions()
@ -594,7 +594,7 @@ def test_properties_cached():
od1 = OptionDescription("opt", "", [OptionDescription("sub", "", [b1])])
cfg = Config(od1)
cfg.property.read_write()
assert cfg.option('sub.b1').property.get() == {'test'}
assert cfg.option('sub.b1').property.get() == {'test', "validator"}
# assert not list_sessions()
@ -603,9 +603,9 @@ def test_append_properties_force_store_value():
gcgroup = OptionDescription('gc', '', [gcdummy])
od1 = OptionDescription('tiramisu', '', [gcgroup])
cfg = Config(od1)
assert cfg.option('gc.dummy').property.get() == {'force_store_value'}
assert cfg.option('gc.dummy').property.get() == {'force_store_value', "validator"}
cfg.option('gc.dummy').property.add('test')
assert cfg.option('gc.dummy').property.get() == {'force_store_value', 'test'}
assert cfg.option('gc.dummy').property.get() == {'force_store_value', 'test', "validator"}
# assert not list_sessions()
@ -818,27 +818,27 @@ def test_reset_properties_force_store_value():
# assert not list_sessions()
#def test_importation_force_store_value():
# gcdummy = BoolOption('dummy', 'dummy', default=False,
# properties=('force_store_value',))
# gcgroup = OptionDescription('gc', '', [gcdummy])
# od1 = OptionDescription('tiramisu', '', [gcgroup])
# config1 = Config(od1)
# assert config1.value.exportation() == {}
# config1.property.add('frozen')
# assert config1.value.exportation() == {}
# config1.property.add('force_store_value')
# assert config1.value.exportation() == {'gc.dummy': {None: [False, 'forced']}}
# exportation = config1.property.exportation()
# config2 = Config(od1)
# assert config2.value.exportation() == {}
# config2.property.importation(exportation)
# assert config2.value.exportation() == {'gc.dummy': {None: [False, 'forced']}}
# config2.property.importation(exportation)
# assert config2.value.exportation() == {'gc.dummy': {None: [False, 'forced']}}
## assert not list_sessions()
#
#
def test_importation_force_store_value():
gcdummy = BoolOption('dummy', 'dummy', default=False,
properties=('force_store_value',))
gcgroup = OptionDescription('gc', '', [gcdummy])
od1 = OptionDescription('tiramisu', '', [gcgroup])
config1 = Config(od1)
assert config1.value.exportation() == {}
config1.property.add('frozen')
assert config1.value.exportation() == {}
config1.property.add('force_store_value')
assert config1.value.exportation() == {'gc.dummy': {None: [False, 'forced']}}
exportation = config1.property.exportation()
config2 = Config(od1)
assert config2.value.exportation() == {}
config2.property.importation(exportation)
assert config2.value.exportation() == {'gc.dummy': {None: [False, 'forced']}}
config2.property.importation(exportation)
assert config2.value.exportation() == {'gc.dummy': {None: [False, 'forced']}}
# assert not list_sessions()
def test_set_modified_value():
gcdummy = BoolOption('dummy', 'dummy', default=False, properties=('force_store_value',))
gcgroup = OptionDescription('gc', '', [gcdummy])
@ -920,18 +920,18 @@ def test_set_modified_value():
# assert not list_sessions()
#def test_none_is_not_modified():
# gcdummy = StrOption('dummy', 'dummy', properties=('force_store_value',))
# gcdummy1 = StrOption('dummy1', 'dummy1', default="str", properties=('force_store_value',))
# gcgroup = OptionDescription('gc', '', [gcdummy, gcdummy1])
# od1 = OptionDescription('tiramisu', '', [gcgroup])
# cfg = Config(od1)
# assert cfg.value.exportation() == {}
# cfg.property.read_write()
# assert cfg.value.exportation() == {'gc.dummy1': {None: ['str', 'forced']}}
## assert not list_sessions()
#
#
def test_none_is_not_modified():
gcdummy = StrOption('dummy', 'dummy', properties=('force_store_value',))
gcdummy1 = StrOption('dummy1', 'dummy1', default="str", properties=('force_store_value',))
gcgroup = OptionDescription('gc', '', [gcdummy, gcdummy1])
od1 = OptionDescription('tiramisu', '', [gcgroup])
cfg = Config(od1)
assert cfg.value.exportation() == {}
cfg.property.read_write()
assert cfg.value.exportation() == {'gc.dummy1': {None: ['str', 'forced']}}
# assert not list_sessions()
def test_pprint():
msg_error = _("cannot access to {0} {1} because has {2} {3}")
msg_is_not = _('the value of "{0}" is not {1}')

View file

@ -34,7 +34,7 @@ def return_val(value, param=None):
def return_if_val(value):
if value != 'val':
if value not in ['val', 'val_not_raise']:
raise ValueError('test error')
@ -121,6 +121,34 @@ def test_validator(config_type):
# assert not list_sessions()
def test_validator_no_validation(config_type):
opt1 = StrOption('opt1', '', validators=[Calculation(return_true, Params(ParamSelfOption()))], default='val', properties=frozenset(['novalidator']))
opt2 = StrOption('opt2', '', validators=[Calculation(return_false, Params(ParamSelfOption()))], properties=frozenset(['novalidator']))
od1 = OptionDescription('root', '', [opt1, opt2])
cfg_ori = Config(od1)
cfg = get_config(cfg_ori, config_type)
assert cfg.option('opt1').value.get() == 'val'
assert cfg.option('opt2').value.valid() is True
cfg.option('opt2').value.set('val')
def test_validator_no_validation2(config_type):
opt1 = StrOption('opt1', '', properties=frozenset(['novalidator']))
od1 = OptionDescription('root', '', [opt1])
cfg_ori = Config(od1)
cfg = get_config(cfg_ori, config_type)
cfg.option('opt1').value.set(1)
assert cfg.option('opt1').value.get() == 1
def test_validator_no_validation3(config_type):
opt1 = StrOption('opt1', '', 1, properties=frozenset(['novalidator']))
od1 = OptionDescription('root', '', [opt1])
cfg_ori = Config(od1)
cfg = get_config(cfg_ori, config_type)
assert cfg.option('opt1').value.get() == 1
def test_validator_not_valid(config_type):
with pytest.raises(ValueError):
StrOption('not_a_list', '', validators=Calculation(return_true, Params(ParamSelfOption())), default='val')
@ -313,6 +341,13 @@ def test_validator_multi(config_type):
with warnings.catch_warnings(record=True) as w:
cfg.option('opt1').value.set(['val', 'val1'])
assert len(w) == 1
with warnings.catch_warnings(record=True) as w:
cfg.option('opt1').value.set(['val1', 'val2'])
assert len(w) == 2
with warnings.catch_warnings(record=True) as w:
# same value twice
cfg.option('opt1').value.set(['val', 'val', 'val_not_raise'])
assert len(w) == 2
# assert not list_sessions()

View file

@ -426,9 +426,9 @@ def test_requires_transitive_unrestraint(config_type):
#
if config_type == 'tiramisu-api':
cfg.send()
assert cfg_ori.option('activate_service_web').property.get() == {'disabled'}
assert cfg_ori.option('activate_service_web').property.get() == {'disabled', "validator"}
# FIXME assert cfg_ori.unrestraint.option('ip_address_service_web').property.get() == {'disabled'}
assert cfg_ori.option('ip_address_service_web').property.get() == {'disabled'}
assert cfg_ori.option('ip_address_service_web').property.get() == {'disabled', "validator"}
# assert not list_sessions()

View file

@ -146,8 +146,8 @@ def test_slots_option_readonly():
with pytest.raises(AttributeError):
q._requires = 'q'
# assert not list_sessions()
#
#
#def test_slots_description():
# # __slots__ for OptionDescription should be complete for __getattr__
# slots = set()

View file

@ -528,43 +528,43 @@ def test_submulti_unique():
# assert not list_sessions()
#def test_multi_submulti_meta():
# multi = StrOption('multi', '', multi=submulti)
# od1 = OptionDescription('od', '', [multi])
# cfg = Config(od1, name='cfg')
# cfg.property.read_write()
# cfg2 = Config(od1)
# cfg2.property.read_write()
# meta = MetaConfig([cfg, cfg2])
# meta.property.read_write()
# meta.option('multi').value.set([['val']])
# assert meta.option('multi').value.get() == [['val']]
# newcfg = meta.config('cfg')
# newcfg.option('multi').value.set([['val', None]])
# assert cfg.option('multi').value.get() == [['val', None]]
# newcfg = meta.config('cfg')
# assert newcfg.option('multi').value.get() == [['val', None]]
# assert meta.option('multi').value.get() == [['val']]
## assert not list_sessions()
#
#
#def test_multi_submulti_meta_no_cache():
# multi = StrOption('multi', '', multi=submulti)
# multi = StrOption('multi', '', multi=submulti)
# od1 = OptionDescription('od', '', [multi])
# cfg = Config(od1, name='cfg')
# cfg.property.read_write()
# cfg2 = Config(od1)
# cfg.property.read_write()
# meta = MetaConfig([cfg, cfg2])
# meta.property.read_write()
# meta.property.remove('cache')
# meta.option('multi').value.set([['val']])
# assert meta.option('multi').value.get() == [['val']]
# newcfg = meta.config('cfg')
# newcfg.option('multi').value.set([['val', None]])
# assert cfg.option('multi').value.get() == [['val', None]]
# newcfg = meta.config('cfg')
# assert newcfg.option('multi').value.get() == [['val', None]]
# assert meta.option('multi').value.get() == [['val']]
## assert not list_sessions()
def test_multi_submulti_meta():
multi = StrOption('multi', '', multi=submulti)
od1 = OptionDescription('od', '', [multi])
cfg = Config(od1, name='cfg')
cfg.property.read_write()
cfg2 = Config(od1)
cfg2.property.read_write()
meta = MetaConfig([cfg, cfg2])
meta.property.read_write()
meta.option('multi').value.set([['val']])
assert meta.option('multi').value.get() == [['val']]
newcfg = meta.config('cfg')
newcfg.option('multi').value.set([['val', None]])
assert cfg.option('multi').value.get() == [['val', None]]
newcfg = meta.config('cfg')
assert newcfg.option('multi').value.get() == [['val', None]]
assert meta.option('multi').value.get() == [['val']]
# assert not list_sessions()
def test_multi_submulti_meta_no_cache():
multi = StrOption('multi', '', multi=submulti)
multi = StrOption('multi', '', multi=submulti)
od1 = OptionDescription('od', '', [multi])
cfg = Config(od1, name='cfg')
cfg.property.read_write()
cfg2 = Config(od1)
cfg.property.read_write()
meta = MetaConfig([cfg, cfg2])
meta.property.read_write()
meta.property.remove('cache')
meta.option('multi').value.set([['val']])
assert meta.option('multi').value.get() == [['val']]
newcfg = meta.config('cfg')
newcfg.option('multi').value.set([['val', None]])
assert cfg.option('multi').value.get() == [['val', None]]
newcfg = meta.config('cfg')
assert newcfg.option('multi').value.get() == [['val', None]]
assert meta.option('multi').value.get() == [['val']]
# assert not list_sessions()

View file

@ -28,6 +28,8 @@ def test_symlink_option(config_type):
assert cfg.option('c').issymlinkoption()
assert cfg.option('s1.b').type() == 'boolean'
assert cfg.option('c').type() == 'boolean'
assert cfg.option('s1.b').type(only_self=True) == 'boolean'
assert cfg.option('c').type(only_self=True) == 'symlink'
assert cfg.option('s1.b').value.get() is False
cfg.option("s1.b").value.set(True)
cfg.option("s1.b").value.set(False)
@ -157,7 +159,7 @@ def test_symlink_getproperties():
od1 = OptionDescription('opt', '', [boolopt, linkopt])
cfg = Config(od1)
cfg.property.read_write()
assert boolopt.impl_getproperties() == linkopt.impl_getproperties() == {'test'}
assert boolopt.impl_getproperties() == linkopt.impl_getproperties() == {'test', "validator"}
# assert boolopt.impl_has_callback() == linkopt.impl_has_callback() == False
# assert not list_sessions()

View file

@ -42,6 +42,7 @@ from .error import ConfigError
from .api import Config, MetaConfig, GroupConfig, MixConfig
from .option import __all__ as all_options
from .setting import owners, groups, undefined
from .__version__ import __version__
allfuncs = [
@ -76,4 +77,3 @@ allfuncs.extend(all_options)
del all_options
__all__ = tuple(allfuncs)
del allfuncs
__version__ = "4.1.0"

1
tiramisu/__version__.py Normal file
View file

@ -0,0 +1 @@
__version__ = "5.2.0a11"

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2017-2025 Team tiramisu (see AUTHORS for all contributors)
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
@ -52,6 +52,13 @@ from .autolib import Calculation
TIRAMISU_VERSION = 5
class Fake_SubConfig:
def __init__(self, config_bag, path, index):
self.config_bag = config_bag
self.path = path
self.index = index
class TiramisuHelp:
_tmpl_help = " {0}\t{1}"
@ -120,13 +127,16 @@ class CommonTiramisu(TiramisuHelp):
def _set_subconfig(self) -> None:
if not self._subconfig:
self._subconfig = self._config_bag.context.get_sub_config(
self._config_bag,
self._path,
self._index,
validate_properties=False,
allow_dynoption=self._allow_dynoption,
)
try:
self._subconfig = self._config_bag.context.get_sub_config(
self._config_bag,
self._path,
self._index,
validate_properties=False,
allow_dynoption=self._allow_dynoption,
)
except AssertionError as err:
raise ConfigError(str(err))
def option_type(typ):
@ -139,6 +149,8 @@ def option_type(typ):
@wraps(func)
def wrapped(*args, **kwargs):
self = args[0]
if isinstance(typ, list) and "allow_dynoption" in typ:
self._allow_dynoption = True
config_bag = self._config_bag
if self._config_bag.context.impl_type == "group" and "group" in types:
options_bag = [
@ -200,7 +212,9 @@ def option_type(typ):
"please specify index with a follower option ({0}.{1})"
).format(self.__class__.__name__, func.__name__)
raise ConfigError(msg)
if self._validate_properties and "dont_validate_property" not in types:
if "validate_properties" in types or (
self._validate_properties and "dont_validate_property" not in types
):
settings = self._config_bag.context.get_settings()
parent = self._subconfig.parent
if parent and parent.transitive_properties:
@ -251,10 +265,13 @@ class _TiramisuOptionWalk:
validate_properties: bool,
*,
uncalculated: bool = False,
with_index: bool = True,
):
options = []
for sub_subconfig in subconfig.get_children(
validate_properties, uncalculated=uncalculated
validate_properties,
uncalculated=uncalculated,
with_index=with_index,
):
options.append(
TiramisuOption(
@ -272,12 +289,28 @@ class _TiramisuOptionOptionDescription:
_validate_properties = False
@option_type(["optiondescription", "option", "with_or_without_index", "symlink"])
@option_type(
[
"optiondescription",
"option",
"with_or_without_index",
"symlink",
"allow_dynoption",
]
)
def get(self):
"""Get Tiramisu option"""
return self._subconfig.option
@option_type(["optiondescription", "option", "with_or_without_index", "symlink"])
@option_type(
[
"optiondescription",
"option",
"with_or_without_index",
"symlink",
"allow_dynoption",
]
)
def isoptiondescription(self):
"""Test if option is an optiondescription"""
return self._subconfig.option.impl_is_optiondescription()
@ -290,11 +323,14 @@ class _TiramisuOptionOptionDescription:
@option_type(["optiondescription", "option", "with_or_without_index", "symlink"])
def description(
self,
with_quote: bool = False,
uncalculated: bool = False,
):
"""Get option description"""
if not uncalculated:
return self._subconfig.option.impl_get_display_name(self._subconfig)
return self._subconfig.option.impl_get_display_name(
self._subconfig, with_quote=with_quote
)
return self._subconfig.option._get_information(
self._subconfig,
"doc",
@ -323,6 +359,15 @@ class _TiramisuOptionOptionDescription:
return self._subconfig.option.impl_getpath()
return self._subconfig.true_path
def parent(self):
parent = self._subconfig.parent
return TiramisuOption(
parent.path,
None,
self._config_bag,
subconfig=parent,
)
@option_type(["optiondescription", "option", "symlink", "with_or_without_index"])
def has_dependency(
self,
@ -332,26 +377,75 @@ class _TiramisuOptionOptionDescription:
return self._subconfig.option.impl_has_dependency(self_is_dep)
@option_type(["optiondescription", "option", "symlink", "with_or_without_index"])
def dependencies(self):
def dependencies(
self,
*,
uncalculated: bool = False,
):
"""Get dependencies from this option"""
options = []
for option in self._subconfig.option.get_dependencies(self._config_bag.context):
options.append(
TiramisuOption(
option().impl_getpath(),
None,
self._config_bag,
allow_dynoption=True,
)
)
context = self._config_bag.context
index = self._index
parent = self._subconfig.parent
parent_option = parent.option
for woption in self._subconfig.option.get_dependencies(
self._config_bag.context
):
option = woption()
if not uncalculated and option.issubdyn():
for subconfig in context.get_dynamic_from_dyn_option(
self._subconfig, option
):
options.append(
TiramisuOption(
subconfig.path,
None,
self._config_bag,
)
)
elif not uncalculated and option.impl_is_dynoptiondescription():
for subconfig in context.get_dynamic_from_dyn_option(
self._subconfig, option
):
options.append(
TiramisuOption(
subconfig.path,
None,
self._config_bag,
)
)
else:
if (
not option.impl_is_optiondescription()
and option.impl_is_follower()
and parent_option.impl_is_leadership()
and parent_option.in_same_leadership(option)
):
if index is not None:
current_indexes = [index]
else:
current_indexes = range(parent.get_length_leadership())
else:
current_indexes = [None]
for current_index in current_indexes:
options.append(
TiramisuOption(
option.impl_getpath(),
current_index,
self._config_bag,
allow_dynoption=uncalculated,
)
)
return options
@option_type(["option", "optiondescription", "symlink", "with_or_without_index"])
def type(self):
def type(self, only_self=False):
"""Get de option type"""
option = self._subconfig.option
if option.impl_is_optiondescription():
return "optiondescription"
if only_self and option.impl_is_symlinkoption():
return "symlink"
return option.get_type()
@option_type(["option", "symlink", "with_or_without_index"])
@ -441,27 +535,27 @@ class _TiramisuOptionOptionDescription:
class _TiramisuOptionOption(_TiramisuOptionOptionDescription):
"""Manage option"""
@option_type(["option", "symlink", "with_or_without_index"])
@option_type(["option", "symlink", "with_or_without_index", "allow_dynoption"])
def ismulti(self):
"""Test if option could have multi value"""
return self._subconfig.option.impl_is_multi()
@option_type(["option", "symlink", "with_or_without_index"])
@option_type(["option", "symlink", "with_or_without_index", "allow_dynoption"])
def issubmulti(self):
"""Test if option could have submulti value"""
return self._subconfig.option.impl_is_submulti()
@option_type(["option", "with_or_without_index", "symlink"])
@option_type(["option", "with_or_without_index", "symlink", "allow_dynoption"])
def isleader(self):
"""Test if option is a leader"""
return self._subconfig.option.impl_is_leader()
@option_type(["option", "with_or_without_index", "symlink"])
@option_type(["option", "with_or_without_index", "symlink", "allow_dynoption"])
def isfollower(self):
"""Test if option is a follower"""
return self._subconfig.option.impl_is_follower()
@option_type(["option", "symlink", "with_or_without_index"])
@option_type(["option", "symlink", "with_or_without_index", "allow_dynoption"])
def issymlinkoption(self) -> bool:
"""Test if option is a symlink option"""
return self._subconfig.option.impl_is_symlinkoption()
@ -483,9 +577,11 @@ class _TiramisuOptionOption(_TiramisuOptionOptionDescription):
return r"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
@option_type(["option", "with_or_without_index", "symlink"])
def index(self):
def index(self, index=None):
"""Get index of option"""
return self._subconfig.index
if index is None:
return self._subconfig.index
return TiramisuOption(self._path, index, self._config_bag)
@option_type(["symlink", "optiondescription"])
def option(self, *args, **kwargs):
@ -531,9 +627,11 @@ class TiramisuOptionOwner(CommonTiramisuOption):
_validate_properties = True
@option_type(["symlink", "option", "with_index"])
def get(self):
def get(self, only_self=False):
"""Get owner for a specified option"""
return self._config_bag.context.get_owner(self._subconfig)
return self._config_bag.context.get_owner(
self._subconfig, validate_meta=not only_self
)
@option_type(["symlink", "option", "with_index"])
def isdefault(self):
@ -582,6 +680,7 @@ class TiramisuOptionProperty(CommonTiramisuOption):
self._subconfig,
uncalculated=uncalculated,
apply_requires=apply_requires,
not_unrestraint=True,
)
@option_type(["option", "optiondescription", "with_or_without_index"])
@ -801,14 +900,11 @@ class TiramisuOptionValue(CommonTiramisuOption, _TiramisuODGet):
option = self._subconfig.option
if (
not isinstance(value, Calculation)
and isinstance(value, list)
and option.impl_is_leader()
and len(value) < self._subconfig.parent.get_length_leadership()
):
raise LeadershipError(
_("cannot reduce length of the leader {}" "").format(
option.impl_get_display_name(self._subconfig, with_quote=True)
)
)
raise LeadershipError(self._subconfig, "leadership-reduce")
values = self._config_bag.context.get_values()
return values.set_value(self._subconfig, value)
@ -906,34 +1002,39 @@ class TiramisuOptionValue(CommonTiramisuOption, _TiramisuODGet):
def mandatory(self):
"""Return path of options with mandatory property without any value"""
subconfig = self._subconfig
if subconfig.option.impl_is_optiondescription():
ori_config_bag = self._subconfig.config_bag
config_bag = ori_config_bag.copy()
config_bag.properties -= {"mandatory", "empty", "warnings"}
config_bag.set_permissive()
self._subconfig.config_bag = config_bag
options = []
for subconfig in self._config_bag.context.walk(
self._subconfig,
only_mandatory=True,
):
options.append(
TiramisuOption(
subconfig.path,
subconfig.index,
ori_config_bag,
subconfig=subconfig,
)
)
self._subconfig.config_bag = ori_config_bag
return options
ori_config_bag = self._subconfig.config_bag
config_bag = ori_config_bag.copy()
config_bag.properties -= {"mandatory", "empty", "warnings"}
config_bag.set_permissive()
self._subconfig.config_bag = config_bag
try:
self._config_bag.context.walk_valid_value(
self._subconfig, only_mandatory=True
)
except PropertiesOptionError as err:
return err.proptype == ["mandatory"] or err.proptype == ["empty"]
return False
if subconfig.option.impl_is_optiondescription():
options = []
for subconfig in config_bag.context.walk(
self._subconfig,
only_mandatory=True,
):
options.append(
TiramisuOption(
subconfig.path,
subconfig.index,
ori_config_bag,
subconfig=subconfig,
)
)
self._subconfig.config_bag = ori_config_bag
return options
try:
self._config_bag.context.walk_valid_value(
self._subconfig, only_mandatory=True
)
except PropertiesOptionError as err:
return err.proptype == ["mandatory"] or err.proptype == ["empty"]
self._subconfig.config_bag = ori_config_bag
return False
except Exception as err:
self._subconfig.config_bag = ori_config_bag
raise err from err
def _registers(
@ -1039,12 +1140,13 @@ class TiramisuOption(
self._set_subconfig()
return self._subconfig.option.impl_get_group_type()
@option_type("optiondescription")
@option_type(["optiondescription", "validate_properties"])
def list(
self,
*,
validate_properties: bool = True,
uncalculated: bool = False,
with_index: bool = True,
):
"""List options inside an option description (by default list only option)"""
self._set_subconfig()
@ -1052,6 +1154,7 @@ class TiramisuOption(
self._subconfig,
validate_properties,
uncalculated=uncalculated,
with_index=with_index,
)
def _load_dict(
@ -1172,6 +1275,8 @@ class TiramisuContextValue(TiramisuConfig, _TiramisuODGet):
self,
path: str,
value: Any,
*,
index: Optional[int] = None,
only_config=undefined,
force_default=undefined,
force_default_if_same=undefined,
@ -1187,14 +1292,22 @@ class TiramisuContextValue(TiramisuConfig, _TiramisuODGet):
kwargs["force_default_if_same"] = force_default_if_same
if force_dont_change_value is not undefined:
kwargs["force_dont_change_value"] = force_dont_change_value
option_bag = OptionBag(
None,
None,
self._config_bag,
path=path,
)
context = self._config_bag.context
if isinstance(context, KernelGroupConfig):
subconfig = Fake_SubConfig(
self._config_bag,
path,
index,
)
else:
subconfig = context.get_sub_config(
self._config_bag,
path,
index,
validate_properties=False,
)
return self._config_bag.context.set_value(
option_bag,
subconfig,
value,
**kwargs,
)
@ -1527,7 +1640,7 @@ class TiramisuContextOption(TiramisuConfig, _TiramisuOptionWalk):
def get(self):
"""Get Tiramisu option"""
return None
return self._config_bag.context.get_description()
def isleadership(self):
"""Test if option is a leader or a follower"""
@ -1582,6 +1695,7 @@ class TiramisuContextOption(TiramisuConfig, _TiramisuOptionWalk):
*,
validate_properties: bool = True,
uncalculated: bool = False,
with_index: bool = True,
):
"""List options (by default list only option)"""
root = self._config_bag.context.get_root(self._config_bag)
@ -1589,6 +1703,7 @@ class TiramisuContextOption(TiramisuConfig, _TiramisuOptionWalk):
root,
validate_properties,
uncalculated=uncalculated,
with_index=with_index,
)
def _load_dict(self, clearable="all", remotable="minimum"):
@ -1895,7 +2010,7 @@ class Config(TiramisuAPI, TiramisuContextOption):
return f"<Config path=None>"
class MetaConfig(TiramisuAPI):
class MetaConfig(TiramisuAPI, TiramisuContextOption):
"""MetaConfig object that enables us to handle the sub configuration's options
with common root optiondescription
"""
@ -1963,7 +2078,7 @@ class MixConfig(TiramisuAPI):
display_name=display_name,
)
settings = config.get_settings()
properties = settings.get_context_properties(config.properties_cache)
properties = settings.get_context_properties()
permissives = settings.get_context_permissives()
config_bag = ConfigBag(
config,

View file

@ -22,7 +22,15 @@ from typing import Any, Optional, Union, Callable, Dict, List
from itertools import chain
import weakref
from .error import PropertiesOptionError, ConfigError, LeadershipError, ValueWarning
from .error import (
PropertiesOptionError,
ConfigError,
LeadershipError,
ValueWarning,
CancelParam,
display_list,
errors,
)
from .i18n import _
from .setting import undefined, ConfigBag
from .function import FUNCTION_WAITING_FOR_DICT, FUNCTION_WAITING_FOR_ERROR
@ -49,6 +57,7 @@ def get_calculated_value(
has_calculation = True
elif isinstance(value, list):
# if value is a list, do subcalculation
value = value.copy()
for idx, val in enumerate(value):
value[idx], _has_calculation = get_calculated_value(
subconfig,
@ -148,11 +157,15 @@ class ParamDynOption(ParamOption):
)
if not isinstance(identifiers, list):
raise Exception(
f"identifiers in ParamDynOption must be a list, not {identifiers}"
_("identifiers in ParamDynOption must be a list, not {0}").format(
identifiers
)
)
if not isinstance(optional, bool):
raise Exception(
f"optional in ParamDynOption must be a boolean, not {optional}"
_("optional in ParamDynOption must be a boolean, not {0}").format(
optional
)
)
self.identifiers = identifiers
self.optional = optional
@ -203,9 +216,11 @@ class ParamInformation(Param):
def set_option(self, option: "Option" = None) -> None:
if not hasattr(self, "self_option"):
raise ConfigError("cannot add option in information after creating config")
raise ConfigError(
_("cannot add option in information after creating config")
)
if self.option:
raise ConfigError("cannot redefine option in information")
raise ConfigError(_("cannot redefine option in information"))
if not option.impl_is_optiondescription():
if option.impl_is_symlinkoption():
raise ValueError(
@ -445,14 +460,7 @@ def manager_callback(
or param.raisepropertyerror
):
raise err from err
display_name = subconfig.option.impl_get_display_name(
subconfig, with_quote=True
)
raise ConfigError(
_("unable to carry out a calculation for {}, {}").format(
display_name, err
)
) from err
raise ConfigError(err)
except ValueError as err:
display_name = subconfig.option.impl_get_display_name(
subconfig, with_quote=True
@ -464,20 +472,15 @@ def manager_callback(
) from err
except AttributeError as err:
if isinstance(param, ParamDynOption) and param.optional:
# cannot acces, simulate a propertyerror
# cannot access, simulate a propertyerror
raise PropertiesOptionError(
subconfig,
["configerror"],
config_bag.context.get_settings(),
)
display_name = subconfig.option.impl_get_display_name(
subconfig, with_quote=True
errors.raise_carry_out_calculation_error(
subconfig, _("unable to get value for calculating {0}, {1}"), err
)
raise ConfigError(
_("unable to get value for calculating {0}, {1}").format(
display_name, err
)
) from err
return value
def get_option_bag(
@ -504,17 +507,18 @@ def manager_callback(
index_,
validate_properties=not self_calc,
properties=properties,
valid_conflict=False,
)
except PropertiesOptionError as err:
# raise PropertiesOptionError (which is catched) because must not add value None in carry_out_calculation
if param.notraisepropertyerror or param.raisepropertyerror:
raise err from err
display_name = option.impl_get_display_name(subconfig, with_quote=True)
raise ConfigError(
_("unable to carry out a calculation for {}, {}").format(
display_name, err
)
) from err
errors.raise_carry_out_calculation_error(
subconfig,
_("unable to carry out a calculation for {0}, {1}"),
err,
option=option,
)
except ValueError as err:
display_name = option.impl_get_display_name(subconfig, with_quote=True)
raise ValueError(
@ -524,18 +528,18 @@ def manager_callback(
) from err
except AttributeError as err:
if isinstance(param, ParamDynOption) and param.optional:
# cannot acces, simulate a propertyerror
# cannot access, simulate a propertyerror
raise PropertiesOptionError(
param,
["configerror"],
config_bag.context.get_settings(),
)
display_name = option.impl_get_display_name(subconfig, with_quote=True)
raise ConfigError(
_("unable to get value for calculating {0}, {1}").format(
display_name, err
)
) from err
errors.raise_carry_out_calculation_error(
subconfig,
_("unable to get value for calculating {0}, {1}"),
err,
option=option,
)
return subsubconfig
if isinstance(param, ParamValue):
@ -552,14 +556,15 @@ def manager_callback(
true_path=subconfig.path,
)
if isinstance(isubconfig, list):
display_name = option.impl_get_display_name(
subconfig, with_quote=True
)
search_name = search_option.impl_get_display_name(
None, with_quote=True
)
raise ConfigError(
f"cannot find information for {display_name}, {search_name} is a dynamic option"
errors.raise_carry_out_calculation_error(
subconfig,
_("cannot find information for {0}, {1} is a dynamic option"),
None,
option=option,
extra_keys=[search_name],
)
else:
isubconfig = get_option_bag(
@ -579,12 +584,12 @@ def manager_callback(
param.default_value,
)
except ValueError as err:
display_name = option.impl_get_display_name(subconfig, with_quote=True)
raise ConfigError(
_("unable to get value for calculating {0}, {1}").format(
display_name, err
)
) from err
errors.raise_carry_out_calculation_error(
subconfig,
_("unable to get value for calculating {0}, {1}"),
err,
option=option,
)
if isinstance(param, ParamIndex):
return index
@ -594,14 +599,17 @@ def manager_callback(
not option.impl_is_optiondescription()
or not option.impl_is_dynoptiondescription()
):
display_name = subconfig.option.impl_get_display_name(
subconfig, with_quote=True
)
raise ConfigError(
errors.raise_carry_out_calculation_error(
subconfig,
_(
"option {0} is not a dynoptiondescription or in a dynoptiondescription"
).format(display_name)
),
None,
option=option,
)
if subconfig.identifiers is None:
# if uncalculated
return
return subconfig.identifiers[param.identifier_index]
if isinstance(param, ParamSelfOption):
@ -673,7 +681,37 @@ def manager_callback(
parent,
)
except AttributeError as err:
raise ConfigError(err) from err
if parent.path:
child_path = parent.path + "." + name
else:
child_path = name
if param.optional:
raise CancelParam(
callbk_option.impl_getpath(), child_path
)
identifiers = doption.get_identifiers(parent)
if not identifiers:
errors.raise_carry_out_calculation_error(
subconfig,
_(
'cannot calculate arguments for {0}, {1} with identifier "{2}", there is no identifiers'
),
err,
extra_keys=[identifier],
)
else:
identifiers_list = display_list(
identifiers, add_quote=True
)
errors.raise_carry_out_calculation_error(
subconfig,
_(
'cannot calculate arguments for {0}, {1} with identifier "{2}", list of valid identifiers: {3}'
),
err,
extra_keys=[identifier, identifiers_list],
)
new_parents.append(
parent.get_child(
doption,
@ -777,8 +815,11 @@ def carry_out_calculation(
and option.impl_is_follower()
and index is None
):
raise ConfigError(
f"the follower {option.impl_get_display_name(subconfig, with_quote=True)} must have index in carry_out_calculation!"
errors.raise_carry_out_calculation_error(
subconfig,
_("the follower {0} must have index in carry_out_calculation!"),
None,
option=option,
)
def fake_items(iterator):
@ -828,6 +869,12 @@ def carry_out_calculation(
args.append(err)
else:
kwargs[key] = err
except CancelParam as err:
if callback.__name__ in FUNCTION_WAITING_FOR_ERROR:
if key is None:
args.append(err)
else:
kwargs[key] = err
ret = calculate(
subconfig,
callback,
@ -843,33 +890,14 @@ def carry_out_calculation(
and option.impl_is_follower()
and not option.impl_is_submulti()
):
if args or kwargs:
raise LeadershipError(
_(
'the "{}" function with positional arguments "{}" '
'and keyword arguments "{}" must not return '
'a list ("{}") for the follower option {}'
""
).format(
callback.__name__,
args,
kwargs,
ret,
option.impl_get_display_name(subconfig, with_quote=True),
)
)
else:
raise LeadershipError(
_(
'the "{}" function must not return a list ("{}") '
"for the follower option {}"
""
).format(
callback.__name__,
ret,
option.impl_get_display_name(subconfig, with_quote=True),
)
)
raise LeadershipError(
subconfig,
"leadership-follower-callback-list",
callback=callback.__name__,
args=args,
kwargs=kwargs,
ret=ret,
)
return ret
@ -893,7 +921,7 @@ def calculate(
except (ValueError, ValueWarning) as err:
if allow_value_error:
if force_value_warning:
raise ValueWarning(str(err))
raise ValueWarning(msg=str(err))
raise err from err
error = err
except ConfigError as err:
@ -902,19 +930,17 @@ def calculate(
error = err
if args or kwargs:
msg = _(
'unexpected error "{0}" in function "{1}" with arguments "{3}" and "{4}" '
"for option {2}"
).format(
str(error),
'unexpected error "{1}" in function "{2}" with arguments "{3}" and "{4}" '
"for option {0}"
)
extra_keys = [
callback.__name__,
subconfig.option.impl_get_display_name(subconfig, with_quote=True),
args,
kwargs,
)
]
else:
msg = _('unexpected error "{0}" in function "{1}" for option {2}' "").format(
str(error),
callback.__name__,
subconfig.option.impl_get_display_name(subconfig, with_quote=True),
)
raise ConfigError(msg) from error
msg = _('unexpected error "{1}" in function "{2}" for option {0}')
extra_keys = [callback.__name__]
errors.raise_carry_out_calculation_error(
subconfig, msg, error, extra_keys=extra_keys
)

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2012-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2012-2025 Team tiramisu (see AUTHORS for all contributors)
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
@ -41,7 +41,7 @@ def get_common_path(path1, path2):
return common_path
if common_path.endswith("."):
return common_path[:-1]
if "." in common_path:
elif "." in common_path:
return common_path.rsplit(".", 1)[0]
return None
@ -91,12 +91,12 @@ class CCache:
if option.issubdyn():
# it's an option in dynoptiondescription, remove cache for all generated option
self.reset_cache_dyn_option(
config_bag,
subconfig,
option,
resetted_opts,
)
elif option.impl_is_dynoptiondescription():
self._reset_cache_dyn_optiondescription(
self.reset_cache_dyn_optiondescription(
option,
config_bag,
resetted_opts,
@ -120,13 +120,7 @@ class CCache:
resetted_opts,
)
def _reset_cache_dyn_optiondescription(
self,
option,
config_bag,
resetted_opts,
):
# reset cache for all chidren
def get_dynamic_from_dyn_optiondescription(self, config_bag, option):
path = option.impl_getpath()
if "." in path:
parent_path = path.rsplit(".", 1)[0]
@ -139,9 +133,21 @@ class CCache:
)
else:
parent_subconfig = self.get_root(config_bag)
for subconfig in parent_subconfig.dyn_to_subconfig(
return parent_subconfig.dyn_to_subconfig(
option,
False,
)
def reset_cache_dyn_optiondescription(
self,
option,
config_bag,
resetted_opts,
):
# reset cache for all chidren
for subconfig in self.get_dynamic_from_dyn_optiondescription(
config_bag,
option,
):
self.reset_one_option_cache(
subconfig,
@ -157,15 +163,20 @@ class CCache:
resetted_opts,
)
def reset_cache_dyn_option(
self,
config_bag,
option,
resetted_opts,
):
currents = [self.get_root(config_bag)]
def get_dynamic_from_dyn_option(self, subconfig, option):
config_bag = subconfig.config_bag
sub_paths = option.impl_getpath()
for sub_path in sub_paths.split("."):
current_paths = subconfig.path.split(".")
current_paths_max_index = len(current_paths) - 1
current_subconfigs = []
parent = subconfig
while True:
current_subconfigs.insert(0, parent)
parent = parent.parent
if parent.path is None:
break
currents = [self.get_root(config_bag)]
for idx, sub_path in enumerate(sub_paths.split(".")):
new_currents = []
for current in currents:
sub_option = current.option.get_child(
@ -175,15 +186,20 @@ class CCache:
allow_dynoption=True,
)
if sub_option.impl_is_dynoptiondescription():
new_currents.extend(
list(
current.dyn_to_subconfig(
sub_option,
False,
if (
idx <= current_paths_max_index
and sub_option == current_subconfigs[idx].option
):
new_currents.append(current_subconfigs[idx])
else:
new_currents.extend(
list(
current.dyn_to_subconfig(
sub_option,
False,
)
)
)
)
else:
new_currents.append(
current.get_child(
@ -194,7 +210,15 @@ class CCache:
),
)
currents = new_currents
for dyn_option_subconfig in currents:
return currents
def reset_cache_dyn_option(
self,
subconfig,
option,
resetted_opts,
):
for dyn_option_subconfig in self.get_dynamic_from_dyn_option(subconfig, option):
self.reset_one_option_cache(
dyn_option_subconfig,
resetted_opts,
@ -227,7 +251,8 @@ class SubConfig:
identifiers: Optional[list[str]],
*,
true_path: Optional[str] = None,
properties: Union[list[str], undefined] = undefined,
# for python 3.9 properties: Union[list[str], undefined] = undefined,
properties=undefined,
validate_properties: bool = True,
) -> None:
self.index = index
@ -347,16 +372,31 @@ class SubConfig:
validate_properties,
*,
uncalculated: bool = False,
with_index: bool = True,
):
if self.option.impl_is_leadership() and not uncalculated:
if self.option.impl_is_leadership() and not uncalculated and with_index:
yield from self.get_leadership_children(validate_properties)
else:
children_name = []
for child in self.option.get_children():
if child.impl_is_dynoptiondescription() and not uncalculated:
yield from self.dyn_to_subconfig(
for dyn_child in self.dyn_to_subconfig(
child,
validate_properties,
)
):
yield dyn_child
if child.could_conflict:
name = dyn_child.path
if name in children_name:
raise ConflictError(
_("option name \"{0}\" is not unique in {1}").format(
name,
self.option.impl_get_display_name(
self, with_quote=True
),
)
)
children_name.append(name)
else:
try:
yield self.get_child(
@ -367,6 +407,18 @@ class SubConfig:
except PropertiesOptionError as err:
if err.proptype in (["mandatory"], ["empty"]):
raise err
if child.could_conflict:
name = child.impl_getpath()
if name in children_name:
raise ConflictError(
_("option name \"{0}\" is not unique in {1}").format(
name,
self.option.impl_get_display_name(
self, with_quote=True
),
)
)
children_name.append(name)
def get_child(
self,
@ -416,13 +468,7 @@ class SubConfig:
length = self.get_length_leadership()
if index >= length:
raise LeadershipError(
_(
'index "{0}" is greater than the leadership length "{1}" for option {2}'
).format(
index,
length,
option.impl_get_display_name(subsubconfig, with_quote=True),
)
subsubconfig, "leadership-greater", index=index, length=length
)
return subsubconfig
@ -541,6 +587,20 @@ class SubConfig:
return subconfigs
return subconfigs[0]
def change_context(self, context) -> "SubConfig":
config_bag = self.config_bag.copy()
config_bag.context = context
return SubConfig(
self.option,
self.index,
self.path,
config_bag,
self.parent,
self.identifiers,
true_path=self.true_path,
validate_properties=False,
)
class _Config(CCache):
"""Sub configuration management entry.
@ -593,80 +653,8 @@ class _Config(CCache):
"""get cache for values"""
return self._impl_values_cache # pylint: disable=no-member
# =============================================================================
# WALK
def find(
self,
option_bag,
bytype,
byname,
byvalue,
raise_if_not_found=True,
only_path=undefined,
only_option=undefined,
with_option=False,
):
"""
convenience method for finding an option that lives only in the subtree
:param first: return only one option if True, a list otherwise
:return: find list or an exception if nothing has been found
"""
# pylint: disable=too-many-arguments,too-many-locals
def _filter_by_value(soption_bag):
value = self.get_value(soption_bag)
if isinstance(value, list):
return byvalue in value
return value == byvalue
found = False
if only_path is not undefined:
def _fake_iter():
yield only_option
options = _fake_iter()
else:
options = option_bag.option.get_children_recursively(
bytype,
byname,
option_bag.config_bag,
)
for option in options:
path = option.impl_getpath()
soption_bag = OptionBag(
option,
None,
option_bag.config_bag,
)
if byvalue is not undefined and not _filter_by_value(soption_bag):
continue
if option_bag.config_bag.properties:
# remove option with propertyerror, ...
try:
self.get_sub_config(
option_bag.config_bag, # pylint: disable=no-member
path,
None,
validate_properties=True,
)
except PropertiesOptionError:
continue
found = True
if not with_option:
yield path
else:
yield path, option
self._find_return_results(
found,
raise_if_not_found,
)
def _find_return_results(self, found, raise_if_not_found):
if not found and raise_if_not_found:
raise AttributeError(_("no option found in config" " with these criteria"))
# # =============================================================================
# # WALK
def walk_valid_value(
self,
subconfig,
@ -711,6 +699,7 @@ class _Config(CCache):
properties=undefined,
true_path: Optional[str] = None,
allow_dynoption: bool = False,
valid_conflict: bool = True,
):
subconfig = self.get_root(config_bag)
if path is None:
@ -743,6 +732,32 @@ class _Config(CCache):
identifier, option = option
else:
identifier = None
if valid_conflict and option.could_conflict:
for ref in option.could_conflict:
child = ref()
if child.impl_is_dynoptiondescription():
for dyn_child in subconfig.dyn_to_subconfig(
child,
validate_properties,
):
if path == dyn_child.path:
raise ConflictError(
_("option name \"{0}\" is not unique in {1}").format(
name,
option.impl_get_display_name(
subconfig, with_quote=True
),
)
)
elif child.impl_getname() == name:
raise ConflictError(
_("option name \"{0}\" is not unique in {1}").format(
name,
self.option.impl_get_display_name(
self, with_quote=True
),
)
)
subconfig = subconfig.get_child(
option,
index_,
@ -821,6 +836,15 @@ class _Config(CCache):
# =============================================================================
# Manage value
def set_value(
self,
subconfig,
value: Any,
) -> Any:
"""set value"""
self.get_settings().validate_properties(subconfig)
return self.get_values().set_value(subconfig, value)
def get_value(
self,
subconfig,
@ -868,9 +892,10 @@ class _Config(CCache):
subconfig, with_quote=True
)
raise LeadershipError(
_(
"the follower option {0} has greater length ({1}) than the leader length ({2})"
).format(option_name, follower_len, length)
subconfig,
"leadership-follower-greater",
index=follower_len,
length=length,
)
self.get_settings().validate_mandatory(
subconfig,
@ -939,6 +964,8 @@ class _Config(CCache):
def get_owner(
self,
subconfig: "SubConfig",
*,
validate_meta=True,
):
"""get owner"""
subconfigs = self._get(
@ -949,13 +976,14 @@ class _Config(CCache):
for sc in subconfigs:
owner = self.get_owner(
sc,
validate_meta=validate_meta,
)
if owner != owners.default:
break
else:
owner = owners.default
else:
owner = self.get_values().getowner(subconfigs)
owner = self.get_values().getowner(subconfigs, validate_meta=validate_meta)
return owner
@ -1236,6 +1264,10 @@ class KernelGroupConfig(_CommonConfig):
# pylint: disable=super-init-not-called
names = []
for child in children:
if not isinstance(child, (KernelConfig, KernelGroupConfig)):
raise TypeError(
_("child must be a Config, GroupConfig, MixConfig or MetaConfig")
)
name_ = child._impl_name
names.append(name_)
if len(names) != len(set(names)):
@ -1263,61 +1295,58 @@ class KernelGroupConfig(_CommonConfig):
def reset_cache(
self,
option_bag,
subconfig,
resetted_opts=None,
):
if resetted_opts is None:
resetted_opts = []
if isinstance(self, KernelMixConfig):
super().reset_cache(
option_bag,
subconfig,
resetted_opts=copy(resetted_opts),
)
for child in self._impl_children:
if option_bag is not None:
coption_bag = option_bag.copy()
cconfig_bag = coption_bag.config_bag.copy()
cconfig_bag.context = child
coption_bag.config_bag = cconfig_bag
if subconfig is not None:
parent_subconfig = subconfig.change_context(child)
else:
coption_bag = None
parent_subconfig = None
child.reset_cache(
coption_bag,
parent_subconfig,
resetted_opts=copy(resetted_opts),
)
def set_value(
self,
option_bag,
subconfig,
value,
only_config=False,
):
"""Setattr not in current KernelGroupConfig, but in each children"""
ret = []
for child in self._impl_children:
cconfig_bag = option_bag.config_bag.copy()
cconfig_bag = subconfig.config_bag.copy()
cconfig_bag.context = child
if isinstance(child, KernelGroupConfig):
ret.extend(
child.set_value(
option_bag,
subconfig,
value,
only_config=only_config,
)
)
else:
settings = child.get_settings()
properties = settings.get_context_properties(child.properties_cache)
properties = settings.get_context_properties()
permissives = settings.get_context_permissives()
cconfig_bag.properties = properties
cconfig_bag.permissives = permissives
try:
# GROUP
coption_bag = child.get_sub_option_bag(
coption_bag = child.get_sub_config(
cconfig_bag,
option_bag.path,
option_bag.index,
False,
subconfig.path,
subconfig.index,
validate_properties=False,
)
child.set_value(
coption_bag,
@ -1327,7 +1356,7 @@ class KernelGroupConfig(_CommonConfig):
# pylint: disable=protected-access
ret.append(
PropertiesOptionError(
err._option_bag,
err._subconfig,
err.proptype,
err._settings,
err._opt_type,
@ -1390,7 +1419,7 @@ class KernelGroupConfig(_CommonConfig):
cconfig_bag.context = child
if cconfig_bag.properties is None:
settings = child.get_settings()
properties = settings.get_context_properties(child.properties_cache)
properties = settings.get_context_properties()
permissives = settings.get_context_permissives()
cconfig_bag.properties = properties
cconfig_bag.permissives = permissives
@ -1424,6 +1453,7 @@ class KernelGroupConfig(_CommonConfig):
def reset(
self,
path: str,
only_children: bool,
config_bag: ConfigBag,
) -> None:
"""reset value for specified path"""
@ -1432,19 +1462,19 @@ class KernelGroupConfig(_CommonConfig):
cconfig_bag = config_bag.copy()
cconfig_bag.context = child
settings = child.get_settings()
properties = settings.get_context_properties(child.properties_cache)
properties = settings.get_context_properties()
permissives = settings.get_context_permissives()
cconfig_bag.properties = properties
cconfig_bag.permissives = permissives
cconfig_bag.remove_validation()
# GROUP
option_bag = child.get_sub_option_bag(
subconfig = child.get_sub_config(
cconfig_bag,
path,
None,
False,
)[-1]
child.get_values().reset(option_bag)
validate_properties=False,
)
child.get_values().reset(subconfig)
def getconfig(
self,
@ -1499,7 +1529,7 @@ class KernelMixConfig(KernelGroupConfig):
def set_value(
self,
option_bag,
subconfig,
value,
only_config=False,
force_default=False,
@ -1530,10 +1560,10 @@ class KernelMixConfig(KernelGroupConfig):
)
)
for child in self._impl_children:
cconfig_bag = option_bag.config_bag.copy()
cconfig_bag = subconfig.config_bag.copy()
cconfig_bag.context = child
settings = child.get_settings()
properties = settings.get_context_properties(child.properties_cache)
properties = settings.get_context_properties()
cconfig_bag.properties = properties
cconfig_bag.permissives = settings.get_context_permissives()
try:
@ -1545,14 +1575,16 @@ class KernelMixConfig(KernelGroupConfig):
not force_default and not force_default_if_same
)
# MIX
moption_bag = obj.get_sub_option_bag(
moption_bag = obj.get_sub_config(
cconfig_bag,
option_bag.path,
option_bag.index,
validate_properties,
)[-1]
subconfig.path,
subconfig.index,
validate_properties=validate_properties,
)
if force_default_if_same:
if not child.get_values().hasvalue(option_bag.path):
if not child.get_values().hasvalue(
subconfig.path, index=subconfig.index
):
child_value = undefined
else:
child_value = child.get_value(moption_bag)
@ -1572,7 +1604,7 @@ class KernelMixConfig(KernelGroupConfig):
# pylint: disable=protected-access
ret.append(
PropertiesOptionError(
err._option_bag,
err._subconfig,
err.proptype,
err._settings,
err._opt_type,
@ -1585,12 +1617,12 @@ class KernelMixConfig(KernelGroupConfig):
try:
# MIX
moption_bag = self.get_sub_option_bag(
option_bag.config_bag,
option_bag.path,
option_bag.index,
not only_config,
)[-1]
moption_bag = self.get_sub_config(
subconfig.config_bag,
subconfig.path,
subconfig.index,
validate_properties=not only_config,
)
if only_config:
ret = super().set_value(
moption_bag,
@ -1619,37 +1651,37 @@ class KernelMixConfig(KernelGroupConfig):
rconfig_bag.remove_validation()
if self.impl_type == "meta":
# MIX
option_bag = self.get_sub_option_bag(
subconfig = self.get_sub_config(
config_bag,
path,
None,
True,
)[-1]
validate_properties=True,
)
elif not only_children:
try:
# MIX
option_bag = self.get_sub_option_bag(
subconfig = self.get_sub_config(
rconfig_bag,
path,
None,
True,
)[-1]
validate_properties=True,
)
except AttributeError:
only_children = True
for child in self._impl_children:
rconfig_bag.context = child
try:
if self.impl_type == "meta":
moption_bag = option_bag
moption_bag = subconfig
moption_bag.config_bag = rconfig_bag
else:
# MIX
moption_bag = child.get_sub_option_bag(
moption_bag = child.get_sub_config(
rconfig_bag,
path,
None,
True,
)[-1]
validate_properties=True,
)
child.get_values().reset(moption_bag)
except AttributeError:
pass
@ -1660,8 +1692,8 @@ class KernelMixConfig(KernelGroupConfig):
rconfig_bag,
)
if not only_children:
option_bag.config_bag = config_bag
self.get_values().reset(option_bag)
subconfig.config_bag = config_bag
self.get_values().reset(subconfig)
def new_config(
self,

View file

@ -18,6 +18,23 @@
import weakref
from .i18n import _
from typing import Literal, Union
TiramisuErrorCode = Literal[
"option-dynamic",
"option-not-found",
"property-frozen",
"property-error",
"property-mandatory",
"leadership-group_type",
"leadership-wrong_property",
"leadership-force_default_on_freeze",
"leadership-greater",
"leadership-follower-greater",
"leadership-follower-callback-list",
]
def display_list(
lst,
@ -69,6 +86,8 @@ class PropertiesOptionError(AttributeError):
orig_opt=None,
help_properties=None,
):
if orig_opt:
raise Exception("a la")
if opt_type:
self._opt_type = opt_type
self._name = name
@ -87,10 +106,23 @@ class PropertiesOptionError(AttributeError):
self.help_properties = help_properties
self._settings = settings
self.msg = None
if not self.help_properties:
self.help_properties = self.proptype
properties = list(self.help_properties)
if properties == ["frozen"]:
self.code = "property-frozen"
elif properties == ["mandatory"]:
self.code = "property-mandatory"
else:
self.code = "property-error"
super().__init__(None)
def set_orig_opt(self, opt):
self._orig_opt = opt
def display_properties(self, force_property=False, add_quote=True):
if force_property:
properties = self.proptype
else:
properties = self.help_properties
return display_list(list(properties), add_quote=add_quote)
def __str__(self):
# this part is a bit slow, so only execute when display
@ -98,42 +130,81 @@ class PropertiesOptionError(AttributeError):
return self.msg
if self._settings is None:
return "error"
if self.help_properties:
properties = list(self.help_properties)
else:
properties = list(self.proptype)
only_one = len(properties) == 1
properties_msg = display_list(properties, add_quote=True)
if only_one:
prop_msg = _("property")
else:
prop_msg = _("properties")
if properties == ["frozen"]:
if self._orig_opt:
msg = _('cannot modify the {0} {1} because "{2}" has {3} {4}')
else:
msg = _("cannot modify the {0} {1} because has {2} {3}")
else:
if self._orig_opt:
msg = _('cannot access to {0} {1} because "{2}" has {3} {4}')
else:
msg = _("cannot access to {0} {1} because has {2} {3}")
arguments = [self._opt_type]
if self._orig_opt:
# FIXME _orig_opt ?
self.msg = msg.format(
self._opt_type,
self._orig_opt.impl_get_display_name(subconfig, with_quote=True),
self._name,
prop_msg,
properties_msg,
arguments.append(
self._orig_opt.impl_get_display_name(subconfig, with_quote=True)
)
arguments.append(self._name)
index = self._subconfig.index
if index is not None:
arguments.append(index)
if self.code == "property-frozen":
if index is not None:
if self._orig_opt:
msg = _(
'cannot modify the {0} {1} at index "{2}" because {3} is frozen'
)
else:
msg = _(
'cannot modify the {0} {1} at index "{2}" because is frozen'
)
else:
if self._orig_opt:
msg = _("cannot modify the {0} {1} because {2} is frozen")
else:
msg = _("cannot modify the {0} {1} because is frozen")
elif self.code == "property-mandatory":
if index is not None:
if self._orig_opt:
msg = _(
'cannot access to {0} {1} at index "{2}" because {3} hasn\'t value'
)
else:
msg = _('{0} {1} at index "{2}" is mandatory but hasn\'t value')
else:
if self._orig_opt:
msg = _("cannot access to {0} {1} because {2} hasn't value")
else:
msg = _("{0} {1} is mandatory but hasn't value")
else:
self.msg = msg.format(self._opt_type, self._name, prop_msg, properties_msg)
if index is not None:
if self._orig_opt:
msg = _(
'cannot access to {0} {1} at index "{2}" because {3} has {4} {5}'
)
else:
msg = _(
'cannot access to {0} {1} at index "{2}" because has {3} {4}'
)
else:
if self._orig_opt:
msg = _("cannot access to {0} {1} because {2} has {3} {4}")
else:
msg = _("cannot access to {0} {1} because has {2} {3}")
only_one = len(self.help_properties) == 1
if only_one:
arguments.append(_("property"))
else:
arguments.append(_("properties"))
arguments.append(self.display_properties())
self.msg = msg.format(*arguments)
del self._opt_type, self._name
del self._settings, self._orig_opt
return self.msg
class AttributeOptionError(AttributeError):
def __init__(self, path: str, code: TiramisuErrorCode) -> None:
self.path = path
self.code = code
def __str__(self) -> str:
if self.code == "option-dynamic":
return _('cannot access to "{0}" it\'s a dynamic option').format(self.path)
return _('"{0}" is not an option').format(self.path)
# ____________________________________________________________
# Exceptions for a Config
class ConfigError(Exception):
@ -158,8 +229,74 @@ class ConflictError(Exception):
# ____________________________________________________________
# miscellaneous exceptions
class LeadershipError(Exception):
"problem with a leadership's value length"
pass
def __init__(
self,
subconfig: Union[str, "SubConfig"],
code,
*,
prop=None,
index=None,
length=None,
callback=None,
args=None,
kwargs=None,
ret=None,
):
if isinstance(subconfig, str):
self.path = self.display_name = subconfig
else:
self.path = subconfig.path
option = subconfig.option
self.display_name = option.impl_get_display_name(subconfig, with_quote=True)
self.code = code
if prop is not None:
self.prop = prop
if index is not None:
self.index = index
if length is not None:
self.length = length
if callback is not None:
self.callback = callback
if args is not None:
self.args = args
if kwargs is not None:
self.kwargs = kwargs
if ret is not None:
self.ret = ret
def __str__(self):
if self.code == "leadership-group_type":
return _('cannot set "group_type" attribute for the Leadership {0}').format(
self.display_name
)
if self.code == "leadership-wrong_property":
return _('the leader {0} cannot have "{1}" property').format(
self.display_name, self.prop
)
if self.code == "leadership-force_default_on_freeze":
return _(
'the leader {0} cannot have "force_default_on_freeze" or "force_metaconfig_on_freeze" property without "frozen"'
).format(self.display_name)
if self.code == "leadership-reduce":
return _("cannot reduce length of the leader {0}").format(self.display_name)
if self.code == "leadership-greater":
return _(
'index "{0}" is greater than the leadership length "{1}" for option {2}'
).format(self.index, self.length, self.display_name)
if self.code == "leadership-follower-greater":
return _(
"the follower option {0} has greater length ({1}) than the leader length ({2})"
).format(self.display_name, self.index, self.length)
if self.code == "leadership-follower-callback-list":
if self.args or self.kwargs:
return _(
'the "{0}" function with positional arguments "{1}" and keyword arguments "{2}" must not return a list ("{3}") for the follower option {4}'
).format(
self.callback, self.args, self.kwargs, self.ret, self.display_name
)
return _(
'the "{0}" function must not return a list ("{1}") for the follower option {2}'
).format(self.callback, self.ret, self.display_name)
class ConstError(TypeError):
@ -172,7 +309,9 @@ class _CommonError:
self.val = val
self.display_type = display_type
self.opt = weakref.ref(opt)
self.name = opt.impl_get_display_name(subconfig)
self.name = opt.impl_get_display_name(subconfig, with_quote=True)
if subconfig:
self.path = subconfig.path
self.err_msg = err_msg
self.index = index
super().__init__(self.err_msg)
@ -181,7 +320,9 @@ class _CommonError:
try:
msg = self.prefix
except AttributeError:
self.prefix = self.tmpl.format(self.val, _(self.display_type), self.name)
self.prefix = self.tmpl.format(
self.val, _(self.display_type), self.name, self.index
)
msg = self.prefix
if self.err_msg:
if msg:
@ -196,13 +337,20 @@ class _CommonError:
class ValueWarning(_CommonError, UserWarning):
tmpl = None
def __init__(self, *args, **kwargs):
def __init__(self, **kwargs):
if ValueWarning.tmpl is None:
ValueWarning.tmpl = _('attention, "{0}" could be an invalid {1} for "{2}"')
if len(args) == 1 and not kwargs:
self.msg = args[0]
if kwargs.get("index") is None:
ValueWarning.tmpl = _(
'attention, "{0}" could be an invalid {1} for {2}'
)
else:
ValueWarning.tmpl = _(
'attention, "{0}" could be an invalid {1} for {2} at index "{3}"'
)
if list(kwargs) == ["msg"]:
self.msg = kwargs["msg"]
else:
super().__init__(*args, **kwargs)
super().__init__(**kwargs)
self.msg = None
def __str__(self):
@ -214,10 +362,13 @@ class ValueWarning(_CommonError, UserWarning):
class ValueOptionError(_CommonError, ValueError):
tmpl = None
def __init__(self, *args, **kwargs):
def __init__(self, **kwargs):
if ValueOptionError.tmpl is None:
ValueOptionError.tmpl = _('"{0}" is an invalid {1} for "{2}"')
super().__init__(*args, **kwargs)
if kwargs.get("index") is None:
self.tmpl = _('"{0}" is an invalid {1} for {2}')
else:
self.tmpl = _('"{0}" is an invalid {1} for {2} at index "{3}"')
super().__init__(**kwargs)
class ValueErrorWarning(ValueWarning):
@ -225,5 +376,45 @@ class ValueErrorWarning(ValueWarning):
def __init__(self, *args, **kwargs):
if ValueErrorWarning.tmpl is None:
ValueErrorWarning.tmpl = _('"{0}" is an invalid {1} for "{2}"')
ValueErrorWarning.tmpl = _('"{0}" is an invalid {1} for {2}')
super().__init__(*args, **kwargs)
class CancelParam(Exception):
def __init__(self, origin_path, current_path):
super().__init__()
self.origin_path = origin_path
self.current_path = current_path
def __ne__(self, value):
return value is None or value == ""
def __eq__(self, value):
return value is None or value == ""
def __bool__(self):
return False
class ValueErrorIndexes(ValueError):
def __init__(self, msg, indexes):
super().__init__(msg)
self.indexes = indexes
class Errors:
@staticmethod
def raise_carry_out_calculation_error(
subconfig, message, original_error, option=None, extra_keys=[]
):
if option is None:
option = subconfig.option
display_name = option.impl_get_display_name(subconfig, with_quote=True)
if original_error:
raise ConfigError(
message.format(display_name, original_error, *extra_keys)
) from original_error
raise ConfigError(message.format(display_name, extra_keys))
errors = Errors()

View file

@ -59,6 +59,7 @@ class Base:
"_dependencies",
"_dependencies_information",
"_identifiers_dependencies",
"could_conflict",
"__weakref__",
)
@ -88,9 +89,12 @@ class Base:
assert isinstance(properties, frozenset), _(
"invalid properties type {0} for {1}," " must be a frozenset"
).format(type(properties), name)
if not self.impl_is_optiondescription() and "novalidator" not in properties:
properties = properties | {"validator"}
_setattr = object.__setattr__
_setattr(self, "_name", name)
_setattr(self, "_informations", {"doc": doc})
_setattr(self, "could_conflict", [])
for prop in properties:
if not isinstance(prop, str):
if not isinstance(prop, Calculation):
@ -186,6 +190,7 @@ class Base:
else:
dico = tuple([keys, tuple(dico.values())])
_setattr(self, "_informations", dico)
_setattr(self, "could_conflict", tuple(self.could_conflict))
extra = getattr(self, "_extra", None)
if extra is not None:
_setattr(

View file

@ -21,11 +21,12 @@
"""ChoiceOption
"""
from typing import Any
from itertools import chain
from ..setting import undefined
from ..i18n import _
from .option import Option
from ..autolib import Calculation, get_calculated_value
from ..autolib import Calculation, get_calculated_value, ParamOption
from ..error import ConfigError, display_list
@ -42,7 +43,11 @@ class ChoiceOption(Option):
"""
:param values: is a list of values the option can possibly take
"""
if not isinstance(values, (Calculation, tuple)):
if isinstance(values, Calculation):
for param in chain(values.params.args, values.params.kwargs.values()):
if isinstance(param, ParamOption):
param.option._add_dependency(self)
elif not isinstance(values, tuple):
raise TypeError(
_("values must be a tuple or a calculation for {0}").format(name)
)

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2017-2025 Team tiramisu (see AUTHORS for all contributors)
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
@ -21,6 +21,7 @@
"""DomainnameOption
"""
import re
import socket
from ipaddress import ip_interface
from typing import Any, Optional, List
@ -53,12 +54,18 @@ class DomainnameOption(StrOption):
type: str = "domainname",
allow_without_dot: bool = False,
allow_startswith_dot: bool = False,
test_existence: bool = False,
_extra: dict = None,
**kwargs,
) -> None:
# pylint: disable=too-many-branches,too-many-locals,too-many-arguments
if _extra is None:
extra = {}
else:
extra = _extra
if type not in ["netbios", "hostname", "domainname"]:
raise ValueError(_("unknown type {0} for hostname").format(type))
extra = {"_dom_type": type}
extra["type"] = type
if not isinstance(allow_ip, bool):
raise ValueError(_("allow_ip must be a boolean"))
if not isinstance(allow_cidr_network, bool):
@ -67,7 +74,8 @@ class DomainnameOption(StrOption):
raise ValueError(_("allow_without_dot must be a boolean"))
if not isinstance(allow_startswith_dot, bool):
raise ValueError(_("allow_startswith_dot must be a boolean"))
extra["_allow_without_dot"] = allow_without_dot
extra["allow_without_dot"] = allow_without_dot
extra["test_existence"] = test_existence
if type == "domainname":
if allow_without_dot:
min_time = 0
@ -76,14 +84,20 @@ class DomainnameOption(StrOption):
regexp = r"((?!-)[a-z0-9-]{{{1},{0}}}\.){{{1},}}[a-z0-9-]{{1,{0}}}".format(
self._get_len(type), min_time
)
msg = _(
'must start with lowercase characters followed by lowercase characters, number, "-" and "." characters are allowed'
)
msg_warning = _(
'must start with lowercase characters followed by lowercase characters, number, "-" and "." characters are recommanded'
)
else:
regexp = r"((?!-)[a-z0-9-]{{1,{0}}})".format(self._get_len(type))
msg = _(
'must start with lowercase characters followed by lowercase characters, number, "-" and "." characters are allowed'
)
msg_warning = _(
'must start with lowercase characters followed by lowercase characters, number, "-" and "." characters are recommanded'
)
msg = _(
'must start with lowercase characters followed by lowercase characters, number and "-" characters are allowed'
)
msg_warning = _(
'must start with lowercase characters followed by lowercase characters, number and "-" characters are recommanded'
)
if allow_ip:
msg = _("could be a IP, otherwise {}").format(msg)
msg_warning = _("could be a IP, otherwise {}").format(msg_warning)
@ -105,15 +119,15 @@ class DomainnameOption(StrOption):
name,
doc,
)
extra["_allow_ip"] = allow_ip
extra["allow_ip"] = allow_ip
if allow_cidr_network:
extra["_network"] = NetworkOption(
name,
doc,
cidr=True,
)
extra["_allow_cidr_network"] = allow_cidr_network
extra["_allow_startswith_dot"] = allow_startswith_dot
extra["allow_cidr_network"] = allow_cidr_network
extra["allow_startswith_dot"] = allow_startswith_dot
super().__init__(
name,
@ -137,13 +151,13 @@ class DomainnameOption(StrOption):
_("invalid length (max {0})" "").format(part_name_length)
)
part_name_length = self._get_len(self.impl_get_extra("_dom_type"))
if self.impl_get_extra("_dom_type") == "domainname":
if not self.impl_get_extra("_allow_without_dot") and not "." in value:
part_name_length = self._get_len(self.impl_get_extra("type"))
if self.impl_get_extra("type") == "domainname":
if not self.impl_get_extra("allow_without_dot") and not "." in value:
raise ValueError(_("must have dot"))
if len(value) > 255:
raise ValueError(_("invalid length (max 255)"))
if self.impl_get_extra("_allow_startswith_dot") and value.startswith("."):
if self.impl_get_extra("allow_startswith_dot") and value.startswith("."):
val = value[1:]
else:
val = value
@ -155,10 +169,22 @@ class DomainnameOption(StrOption):
_valid_length(dom)
else:
_valid_length(value)
self._validate_domain_resolution(value)
def _validate_domain_resolution(self, value: str) -> None:
if not value.startswith(".") and self.impl_get_extra("test_existence") is True:
try:
socket.gethostbyname(value)
except socket.gaierror as err:
raise ValueError(_("DNS resolution failed").format(value)) from err
except Exception as err:
raise ValueError(
_("error resolving DNS: {1}").format(value, err)
) from err
def _validate_ip_network(self, value: str) -> None:
allow_ip = self.impl_get_extra("_allow_ip")
allow_cidr_network = self.impl_get_extra("_allow_cidr_network")
allow_ip = self.impl_get_extra("allow_ip")
allow_cidr_network = self.impl_get_extra("allow_cidr_network")
if allow_ip is False and allow_cidr_network is False:
raise ValueError(_("must not be an IP"))
if allow_ip is True:
@ -184,7 +210,7 @@ class DomainnameOption(StrOption):
def _second_level_validation_domain(self, value: str, warnings_only: bool) -> None:
if self.impl_get_extra("_has_upper").search(value):
raise ValueError(_("some characters are uppercase"))
if self.impl_get_extra("_allow_startswith_dot") and value.startswith("."):
if self.impl_get_extra("allow_startswith_dot") and value.startswith("."):
val = value[1:]
else:
val = value
@ -200,8 +226,8 @@ class DomainnameOption(StrOption):
def _second_level_validation_ip_network(
self, value: str, warnings_only: bool
) -> None:
allow_ip = self.impl_get_extra("_allow_ip")
allow_cidr_network = self.impl_get_extra("_allow_cidr_network")
allow_ip = self.impl_get_extra("allow_ip")
allow_cidr_network = self.impl_get_extra("allow_cidr_network")
# it's an IP so validate with IPOption
if allow_ip is True and allow_cidr_network is False:
try:

View file

@ -82,6 +82,9 @@ class DynOptionDescription(OptionDescription):
identifier = identifier.replace(".", "_")
return identifier
def name_could_conflict(self, dynchild, child):
return child.impl_getname().startswith(dynchild.impl_getname())
def impl_is_dynoptiondescription(self) -> bool:
return True
@ -124,7 +127,6 @@ class DynOptionDescription(OptionDescription):
)[0]
if values is None:
values = []
values_ = []
if __debug__:
if not isinstance(values, list):
raise ValueError(
@ -134,6 +136,7 @@ class DynOptionDescription(OptionDescription):
self.impl_get_display_name(subconfig, with_quote=True), values
)
)
values_ = []
for val in values:
cval = self.convert_identifier_to_path(val)
if not isinstance(cval, str) or re.match(NAME_REGEXP, cval) is None:
@ -145,7 +148,11 @@ class DynOptionDescription(OptionDescription):
)
else:
values_.append(val)
if __debug__ and len(values_) > len(set(values_)):
if (
__debug__
and "demoting_error_warning" not in subconfig.config_bag.properties
and len(values_) > len(set(values_))
):
raise ValueError(
_(
'DynOptionDescription "{0}" identifiers return a list with same values "{1}"'

View file

@ -52,9 +52,9 @@ class FilenameOption(StrOption):
if typ not in ["file", "directory"]:
raise ValueError(f'unknown type "{typ}" for "{name}"')
extra = {
"_allow_relative": allow_relative,
"_test_existence": test_existence,
"_types": types,
"allow_relative": allow_relative,
"test_existence": test_existence,
"types": types,
}
super().__init__(name, *args, extra=extra, **kwargs)
@ -63,10 +63,10 @@ class FilenameOption(StrOption):
value: str,
) -> None:
super().validate(value)
if not self.impl_get_extra("_allow_relative") and not value.startswith("/"):
if not self.impl_get_extra("allow_relative") and not value.startswith("/"):
raise ValueError(_('must starts with "/"'))
if value is not None and self.impl_get_extra("_test_existence"):
types = self.impl_get_extra("_types")
if value is not None and self.impl_get_extra("test_existence"):
types = self.impl_get_extra("types")
file = Path(value)
found = False
if "file" in types and file.is_file():
@ -74,8 +74,12 @@ class FilenameOption(StrOption):
if not found and "directory" in types and file.is_dir():
found = True
if not found:
translated_types = [
{"file": _("file"), "directory": _("directory")}.get(typ)
for typ in types
]
raise ValueError(
_('cannot find {0} "{1}"').format(
display_list(types, separator="or"), value
_("cannot find this {0}").format(
display_list(translated_types, separator="or"), value
)
)

View file

@ -30,12 +30,16 @@ class IntOption(Option):
__slots__ = tuple()
_type = "integer"
def __init__(self, *args, min_number=None, max_number=None, **kwargs):
def __init__(self, *args, min_number=None, max_number=None, min_integer=None, max_integer=None, **kwargs):
extra = {}
if min_number is not None:
extra["min_number"] = min_number
extra["min_integer"] = min_number
if min_integer is not None:
extra["min_integer"] = min_integer
if max_number is not None:
extra["max_number"] = max_number
extra["max_integer"] = max_number
if max_integer is not None:
extra["max_integer"] = max_integer
super().__init__(*args, extra=extra, **kwargs)
def validate(
@ -43,20 +47,20 @@ class IntOption(Option):
value: int,
) -> None:
if not isinstance(value, int):
raise ValueError()
raise ValueError(_("which is not an integer"))
def second_level_validation(self, value, warnings_only):
min_number = self.impl_get_extra("min_number")
if min_number is not None and value < min_number:
min_integer = self.impl_get_extra("min_integer")
if min_integer is not None and value < min_integer:
if warnings_only:
msg = _('value should be equal or greater than "{0}"')
else:
msg = _('value must be equal or greater than "{0}"')
raise ValueError(msg.format(min_number))
max_number = self.impl_get_extra("max_number")
if max_number is not None and value > max_number:
raise ValueError(msg.format(min_integer))
max_integer = self.impl_get_extra("max_integer")
if max_integer is not None and value > max_integer:
if warnings_only:
msg = _('value should be less than "{0}"')
else:
msg = _('value must be less than "{0}"')
raise ValueError(msg.format(max_number))
raise ValueError(msg.format(max_integer))

View file

@ -43,9 +43,9 @@ class IPOption(StrOption):
):
if extra is None:
extra = {}
extra["_private_only"] = private_only
extra["_allow_reserved"] = allow_reserved
extra["_cidr"] = cidr
extra["private_only"] = private_only
extra["allow_reserved"] = allow_reserved
extra["cidr"] = cidr
super().__init__(*args, extra=extra, **kwargs)
def _validate_cidr(self, value):
@ -66,7 +66,7 @@ class IPOption(StrOption):
def validate(self, value: str) -> None:
super().validate(value)
if self.impl_get_extra("_cidr"):
if self.impl_get_extra("cidr"):
if "/" not in value:
raise ValueError(_('CIDR address must have a "/"'))
self._validate_cidr(value)
@ -75,13 +75,13 @@ class IPOption(StrOption):
def second_level_validation(self, value: str, warnings_only: bool) -> None:
ip_obj = ip_interface(value)
if not self.impl_get_extra("_allow_reserved") and ip_obj.is_reserved:
if not self.impl_get_extra("allow_reserved") and ip_obj.is_reserved:
if warnings_only:
msg = _("shouldn't be reserved IP")
else:
msg = _("mustn't be reserved IP")
raise ValueError(msg)
if self.impl_get_extra("_private_only") and not ip_obj.is_private:
if self.impl_get_extra("private_only") and not ip_obj.is_private:
if warnings_only:
msg = _("should be private IP")
else:

View file

@ -51,9 +51,7 @@ class Leadership(OptionDescription):
**kwargs,
) -> None:
if "group_type" in kwargs:
raise LeadershipError(
_('cannot set "group_type" attribute for a Leadership')
)
raise LeadershipError(name, "leadership-group_type")
super().__init__(
name,
doc,
@ -85,9 +83,7 @@ class Leadership(OptionDescription):
if prop not in ALLOWED_LEADER_PROPERTIES and not isinstance(
prop, Calculation
):
raise LeadershipError(
_('leader cannot have "{}" property').format(prop)
)
raise LeadershipError(name, "leadership-wrong_property", prop=prop)
def _check_child_is_valid(
self,
@ -112,7 +108,7 @@ class Leadership(OptionDescription):
if not child.impl_is_multi():
raise ValueError(
_(
"only multi option allowed in leadership {0} but option "
"only multi option are allowed in leadership {0} but option "
"{1} is not a multi"
""
).format(

View file

@ -32,14 +32,14 @@ class NetworkOption(StrOption):
_type = "network address"
def __init__(self, *args, cidr=False, **kwargs):
extra = {"_cidr": cidr}
extra = {"cidr": cidr}
super().__init__(*args, extra=extra, **kwargs)
def validate(self, value: str) -> None:
super().validate(value)
if value.count(".") != 3:
raise ValueError()
cidr = self.impl_get_extra("_cidr")
cidr = self.impl_get_extra("cidr")
if cidr:
if "/" not in value:
raise ValueError(_("must use CIDR notation"))

View file

@ -127,11 +127,6 @@ class Option(BaseOption):
def test_multi_value(value):
if isinstance(value, Calculation):
return
# option_bag = OptionBag(self,
# None,
# undefined,
# properties=None,
# )
try:
self.validate(value)
self.validate_with_option(
@ -183,16 +178,19 @@ class Option(BaseOption):
# undefined,
# properties=None,
# )
self_properties = getattr(self, "_properties", {})
self.impl_validate(
None,
default,
loaded=True,
self_properties=self_properties,
)
self.impl_validate(
None,
default,
check_error=False,
loaded=True,
self_properties=self_properties,
)
self.value_dependencies(default)
if (is_multi and default != []) or (not is_multi and default is not None):
@ -261,33 +259,40 @@ class Option(BaseOption):
*,
check_error: bool = True,
loaded: bool = False,
self_properties: frozenset = frozenset(),
) -> bool:
"""Return True if value is really valid
If not validate or invalid return it returns False
"""
if (
check_error
and subconfig
and not "validator" in subconfig.config_bag.properties
):
return False
if check_error:
if subconfig:
config_properties = subconfig.config_bag.properties
self_properties = subconfig.properties
else:
config_properties = {"validator"}
if (
"validator" not in config_properties
or "validator" not in self_properties
):
return False
if subconfig:
force_index = subconfig.index
else:
force_index = None
is_warnings_only = getattr(self, "_warnings_only", False)
def _is_not_unique(value):
# if set(value) has not same length than value
def _is_not_unique(current_value, values):
if current_value is None:
return
if not subconfig or not check_error or "unique" not in subconfig.properties:
return
lvalue = [val for val in value if val is not None]
if len(set(lvalue)) == len(lvalue):
return
for idx, val in enumerate(value):
if val not in value[idx + 1 :]:
continue
raise ValueError(_('the value "{}" is not unique' "").format(val))
indexes = [
index for index, value in enumerate(values) if value == current_value
]
if len(indexes) > 1:
raise ValueError(
_('the value "{}" is not unique' "").format(current_value)
)
def calculation_validator(
val,
@ -329,12 +334,12 @@ class Option(BaseOption):
except ValueWarning as warn:
warnings.warn_explicit(
ValueWarning(
subconfig,
val,
_(self.get_type()),
self,
str(warn),
_index,
subconfig=subconfig,
val=val,
display_type=_(self.get_type()),
opt=self,
err_msg=str(warn),
index=_index,
),
ValueWarning,
self.__class__.__name__,
@ -370,12 +375,12 @@ class Option(BaseOption):
if is_warnings_only:
warnings.warn_explicit(
ValueWarning(
subconfig,
_value,
_(self.get_type()),
self,
str(err),
_index,
subconfig=subconfig,
val=_value,
display_type=_(self.get_type()),
opt=self,
err_msg=str(err),
index=_index,
),
ValueWarning,
self.__class__.__name__,
@ -390,70 +395,100 @@ class Option(BaseOption):
_index,
)
val = value
err_index = force_index
try:
if not self.impl_is_multi():
ret = True
if not self.impl_is_multi():
try:
do_validation(
val,
value,
None,
)
elif force_index is not None:
if self.impl_is_submulti():
if not isinstance(value, list):
raise ValueError(_("which must be a list"))
for val in value:
except ValueError as err:
self.validate_parse_error(value, err_index, err, subconfig)
ret = False
elif force_index is not None:
if self.impl_is_submulti():
if not isinstance(value, list):
raise ValueError(_("which must be a list"))
for val in value:
try:
do_validation(
val,
force_index,
)
_is_not_unique(value)
else:
_is_not_unique(val, value)
except ValueError as err:
self.validate_parse_error(val, err_index, err, subconfig)
ret = False
else:
try:
do_validation(
val,
value,
force_index,
)
elif isinstance(value, Calculation) and not subconfig:
pass
elif self.impl_is_submulti():
for err_index, lval in enumerate(value):
if isinstance(lval, Calculation):
continue
if not isinstance(lval, list):
raise ValueError(
_('which "{}" must be a list of list' "").format(lval)
)
for val in lval:
except ValueError as err:
self.validate_parse_error(value, err_index, err, subconfig)
ret = False
elif isinstance(value, Calculation) and not subconfig:
pass
elif self.impl_is_submulti():
for err_index, lval in enumerate(value):
if isinstance(lval, Calculation):
continue
if not isinstance(lval, list):
raise ValueError(
_('which "{}" must be a list of list' "").format(lval)
)
for val in lval:
try:
do_validation(val, err_index)
_is_not_unique(lval)
elif not isinstance(value, list):
raise ValueError(_("which must be a list"))
else:
# FIXME suboptimal, not several time for whole=True!
for err_index, val in enumerate(value):
_is_not_unique(val, lval)
except ValueError as err:
self.validate_parse_error(val, err_index, err, subconfig)
ret = False
elif not isinstance(value, list):
raise ValueError(_("which must be a list"))
else:
# FIXME suboptimal, not several time for whole=True!
for err_index, val in enumerate(value):
try:
do_validation(
val,
err_index,
)
_is_not_unique(value)
except ValueError as err:
if (
not subconfig
or "demoting_error_warning" not in subconfig.config_bag.properties
):
raise ValueOptionError(
subconfig, val, _(self.get_type()), self, str(err), err_index
) from err
warnings.warn_explicit(
ValueErrorWarning(
subconfig, val, _(self.get_type()), self, str(err), err_index
),
ValueErrorWarning,
self.__class__.__name__,
0,
)
return False
return True
_is_not_unique(val, value)
except ValueError as err:
self.validate_parse_error(val, err_index, err, subconfig)
ret = False
# return False
return ret
def validate_parse_error(self, val, index, err, subconfig):
if (
not subconfig
or "demoting_error_warning" not in subconfig.config_bag.properties
):
raise ValueOptionError(
subconfig=subconfig,
val=val,
display_type=_(self.get_type()),
opt=self,
err_msg=str(err),
index=index,
) from err
warnings.warn_explicit(
ValueErrorWarning(
subconfig=subconfig,
val=val,
display_type=_(self.get_type()),
opt=self,
err_msg=str(err),
index=index,
),
ValueErrorWarning,
self.__class__.__name__,
0,
)
def validate_with_option(
self,

View file

@ -29,7 +29,7 @@ from ..setting import ConfigBag, groups, undefined, owners, Undefined
from .baseoption import BaseOption
# from .syndynoption import SubDynOptionDescription, SynDynOptionDescription
from ..error import ConfigError, ConflictError
from ..error import ConfigError, ConflictError, AttributeOptionError
class CacheOptionDescription(BaseOption):
@ -106,7 +106,24 @@ class CacheOptionDescription(BaseOption):
if "force_store_value" in properties:
force_store_values.append(option)
if option.impl_is_readonly():
raise ConflictError(_("duplicate option: {0}").format(option))
previous_path = option.impl_getpath()
if "." in previous_path:
previous_path = _('"{0}" option description').format(
previous_path.rsplit(".", 1)[0]
)
else:
previous_path = _("root option description")
if currpath:
current_path = _('"{0}" option description').format(
".".join(currpath)
)
else:
current_path = _("root option description")
raise ConflictError(
_('option "{0}" is include in {1} but is also in {2}').format(
option.impl_get_display_name(None), current_path, previous_path
)
)
if not self.impl_is_readonly() and display_name:
option._display_name_function = (
display_name # pylint: disable=protected-access
@ -232,19 +249,20 @@ class OptionDescriptionWalk(CacheOptionDescription):
def get_child_not_dynamic(
self,
name,
allow_dynoption,
name: str,
allow_dynoption: bool,
parent: "SubConfig",
):
if name in self._children[0]: # pylint: disable=no-member
option = self._children[1][
self._children[0].index(name)
] # pylint: disable=no-member
if option.impl_is_dynoptiondescription() and not allow_dynoption:
raise AttributeError(
_(
'unknown option "{0}" in root optiondescription (it\'s a dynamic option)'
).format(name)
)
if parent.path:
path = parent.path + "." + name
else:
path = name
raise AttributeOptionError(path, "option-dynamic")
return option
def get_child(
@ -261,6 +279,7 @@ class OptionDescriptionWalk(CacheOptionDescription):
option = self.get_child_not_dynamic(
name,
allow_dynoption,
parent,
)
if option:
return option
@ -274,45 +293,16 @@ class OptionDescriptionWalk(CacheOptionDescription):
if not with_identifier:
return child
return identifier, child
if self.impl_get_group_type() == groups.root: # pylint: disable=no-member
raise AttributeError(
_('unknown option "{0}" in root optiondescription').format(name)
)
raise AttributeError(
_('unknown option "{0}" in optiondescription {1}').format(
name, self.impl_get_display_name(parent, with_quote=True)
)
)
if parent.path is None:
path = name
else:
path = parent.path + "." + name
raise AttributeOptionError(path, "option-not-found")
def get_children(self) -> List[BaseOption]:
"""get children"""
return self._children[1]
def get_children_recursively(
self,
bytype: Optional[BaseOption],
byname: Optional[str],
config_bag: ConfigBag,
self_opt: BaseOption = None,
*,
option_identifiers: Optional[list] = None,
) -> Iterator[Union[BaseOption]]:
"""get children recursively"""
if self_opt is None:
self_opt = self
for option in self_opt.get_children():
if option.impl_is_optiondescription():
for subopt in option.get_children_recursively(
bytype,
byname,
config_bag,
):
yield subopt
elif (byname is None or option.impl_getname() == byname) and (
bytype is None or isinstance(option, bytype)
):
yield option
class OptionDescription(OptionDescriptionWalk):
"""Config's schema (organisation, group) and container of Options
@ -345,35 +335,38 @@ class OptionDescription(OptionDescriptionWalk):
properties=properties,
)
child_names = []
if __debug__:
dynopt_names = []
fix_child_names = []
dynopts = []
for child in children:
name = child.impl_getname()
child_names.append(name)
if __debug__ and child.impl_is_dynoptiondescription():
dynopt_names.append(name)
if child.impl_is_dynoptiondescription():
dynopts.append(child)
else:
fix_child_names.append(name)
# before sorting
children_ = (tuple(child_names), tuple(children))
if __debug__:
# better performance like this
child_names.sort()
old = None
for child in child_names:
if child == old:
raise ConflictError(
_("duplicate option name: " '"{0}"').format(child)
# better performance like this
fix_child_names.sort()
old = None
for child_name in fix_child_names:
if child_name == old:
raise ConflictError(
_('the option name "{0}" is duplicate in "{1}"').format(
child_name, self.impl_get_display_name(None)
)
if dynopt_names:
for dynopt in dynopt_names:
if child != dynopt and child.startswith(dynopt):
raise ConflictError(
_(
'the option\'s name "{0}" start as the dynoptiondescription\'s name "{1}"'
).format(child, dynopt)
)
old = child
)
old = child_name
for dynopt in dynopts:
if dynopt.could_conflict:
continue
for child in children:
if child != dynopt and dynopt.name_could_conflict(dynopt, child):
dynopt.could_conflict.append(weakref.ref(child))
child.could_conflict.append(weakref.ref(dynopt))
break
self._children = children_
# the group_type is useful for filtering OptionDescriptions in a config
self._group_type = None

View file

@ -20,8 +20,6 @@
# ____________________________________________________________
"""PortOption
"""
import re
from ..i18n import _
from .stroption import StrOption
@ -38,7 +36,6 @@ class PortOption(StrOption):
"""
__slots__ = tuple()
port_re = re.compile(r"^[0-9]*$")
_type = "port"
def __init__(
@ -50,15 +47,21 @@ class PortOption(StrOption):
allow_registred: bool = True,
allow_protocol: bool = False,
allow_private: bool = False,
_extra: dict = None,
**kwargs,
) -> None:
extra = {
"_allow_range": allow_range,
"_allow_protocol": allow_protocol,
"_min_value": None,
"_max_value": None,
}
if _extra is None:
extra = {}
else:
extra = _extra
extra["allow_range"] = allow_range
extra["allow_protocol"] = allow_protocol
extra["allow_zero"] = allow_zero
extra["allow_wellknown"] = allow_wellknown
extra["allow_registred"] = allow_registred
extra["allow_private"] = allow_private
extra["_min_value"] = None
extra["_max_value"] = None
ports_min = [0, 1, 1024, 49152]
ports_max = [0, 1023, 49151, 65535]
is_finally = False
@ -82,11 +85,11 @@ class PortOption(StrOption):
def validate(self, value: str) -> None:
super().validate(value)
if self.impl_get_extra("_allow_protocol") and (
if self.impl_get_extra("allow_protocol") and (
value.startswith("tcp:") or value.startswith("udp:")
):
value = [value[4:]]
elif self.impl_get_extra("_allow_range") and ":" in str(value):
elif self.impl_get_extra("allow_range") and ":" in str(value):
value = value.split(":")
if len(value) != 2:
raise ValueError(_("range must have two values only"))
@ -98,11 +101,11 @@ class PortOption(StrOption):
value = [value]
for val in value:
if not self.port_re.search(val):
if not val.isdecimal():
raise ValueError()
def second_level_validation(self, value: str, warnings_only: bool) -> None:
if self.impl_get_extra("_allow_protocol") and (
if self.impl_get_extra("allow_protocol") and (
value.startswith("tcp:") or value.startswith("udp:")
):
value = [value[4:]]

View file

@ -38,7 +38,7 @@ class StrOption(Option):
) -> None:
"""validation"""
if not isinstance(value, str):
raise ValueError()
raise ValueError(_("which is not a string"))
class RegexpOption(StrOption):

View file

@ -54,24 +54,25 @@ class URLOption(StrOption):
**kwargs,
) -> None:
# pylint: disable=too-many-arguments,too-many-locals,redefined-builtin
extra = {
"_domainname": DomainnameOption(
name,
doc,
allow_ip=allow_ip,
type=type,
allow_without_dot=allow_without_dot,
),
"_port": PortOption(
name,
doc,
allow_range=allow_range,
allow_zero=allow_zero,
allow_wellknown=allow_wellknown,
allow_registred=allow_registred,
allow_private=allow_private,
),
}
extra = {}
extra["_domainname"] = DomainnameOption(
name,
doc,
allow_ip=allow_ip,
type=type,
allow_without_dot=allow_without_dot,
_extra=extra,
)
extra["_port"] = PortOption(
name,
doc,
allow_range=allow_range,
allow_zero=allow_zero,
allow_wellknown=allow_wellknown,
allow_registred=allow_registred,
allow_private=allow_private,
_extra=extra,
)
super().__init__(
name,
doc,
@ -110,10 +111,18 @@ class URLOption(StrOption):
domain, port, files = self._get_domain_port_files(value)
# validate port
portoption = self.impl_get_extra("_port")
portoption.validate(port)
try:
portoption.validate(port)
except ValueError as err:
msg = _('the port "{0}" is invalid: {1}').format(domain, err)
raise ValueError(msg) from err
# validate domainname
domainnameoption = self.impl_get_extra("_domainname")
domainnameoption.validate(domain)
try:
domainnameoption.validate(domain)
except ValueError as err:
msg = _('the domain "{0}" is invalid: {1}').format(domain, err)
raise ValueError(msg) from err
# validate files
if files is not None and files != "" and not self.path_re.search(files):
raise ValueError(_("must ends with a valid resource name"))

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"sets the options of the configuration objects Config object itself"
# Copyright (C) 2012-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2012-2025 Team tiramisu (see AUTHORS for all contributors)
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
@ -94,7 +94,7 @@ EXPIRATION_TIME = 5
# demoting_error_warning
# all value errors are convert to warning (ValueErrorWarning)
DEFAULT_PROPERTIES = frozenset(["cache", "validator", "warnings"])
SPECIAL_PROPERTIES = {"frozen", "mandatory", "empty", "force_store_value"}
SPECIAL_PROPERTIES = {"frozen", "mandatory", "empty", "force_store_value", "validator"}
# Config can be in two defaut mode:
#
@ -149,6 +149,7 @@ FORBIDDEN_SET_PERMISSIVES = frozenset(
"force_default_on_freeze",
"force_metaconfig_on_freeze",
"force_store_value",
"validator",
]
)
ALLOWED_LEADER_PROPERTIES = {
@ -158,6 +159,8 @@ ALLOWED_LEADER_PROPERTIES = {
"unique",
"force_store_value",
"mandatory",
"validator",
"novalidator",
"force_default_on_freeze",
"force_metaconfig_on_freeze",
"frozen",
@ -473,7 +476,7 @@ class Settings:
and new_prop not in ALLOWED_LEADER_PROPERTIES
):
raise LeadershipError(
_('leader cannot have "{new_prop}" property')
subconfig, "leadership-wrong_property", prop=new_prop
)
props.add(new_prop)
props -= self.getpermissives(subconfig)
@ -561,19 +564,15 @@ class Settings:
not_allowed_properties = properties - ALLOWED_LEADER_PROPERTIES
if not_allowed_properties:
raise LeadershipError(
_('leader cannot have "{0}" property').format(
display_list(not_allowed_properties)
)
subconfig,
"leadership-wrong_property",
prop=display_list(not_allowed_properties),
)
if (
"force_default_on_freeze" in properties
or "force_metaconfig_on_freeze" in properties
) and "frozen" not in properties:
raise LeadershipError(
_(
'a leader ({0}) cannot have "force_default_on_freeze" or "force_metaconfig_on_freeze" property without "frozen"'
).format(opt.impl_get_display_name())
)
raise LeadershipError(subconfig, "leadership-force_default_on_freeze")
self._properties.setdefault(subconfig.path, {})[subconfig.index] = properties
# values too because of follower values could have a PropertiesOptionError has value
subconfig.config_bag.context.reset_cache(subconfig)

View file

@ -646,9 +646,9 @@ class TiramisuDict:
if self.remotable == "all" or childapi.has_dependency():
obj_form["remote"] = True
if childtype == "IPOption" and (
child.impl_get_extra("_private_only")
or not child.impl_get_extra("_allow_reserved")
or child.impl_get_extra("_cidr")
child.impl_get_extra("private_only")
or not child.impl_get_extra("allow_reserved")
or child.impl_get_extra("cidr")
):
obj_form["remote"] = True
if childtype == "DateOption":

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"takes care of the option's values and multi values"
# Copyright (C) 2013-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2013-2025 Team tiramisu (see AUTHORS for all contributors)
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
@ -189,10 +189,9 @@ class Values:
if subconfig.config_bag.context.impl_type == "config":
return True
# it's a not a config, force to metaconfig only in *explicitly* set
return "force_metaconfig_on_freeze" in settings.get_stored_properties(
return "force_metaconfig_on_freeze" in settings.get_personalize_properties(
subconfig.path,
subconfig.index,
frozenset(),
)
return False
@ -265,9 +264,10 @@ class Values:
) -> None:
"""set value to option"""
owner = self.get_context_owner()
self_properties = subconfig.properties
setting_properties = subconfig.config_bag.properties
ori_value = value
if "validator" in setting_properties:
if "validator" in setting_properties and "validator" in self_properties:
value, has_calculation = self.setvalue_validation(
subconfig,
value,
@ -295,6 +295,7 @@ class Values:
)
validator = (
"validator" in setting_properties
and "validator" in self_properties
and "demoting_error_warning" not in setting_properties
)
if validator and not has_calculation:
@ -304,7 +305,11 @@ class Values:
value,
validated=validator,
)
elif "validator" in setting_properties and has_calculation:
elif (
"validator" in setting_properties
and "validator" in self_properties
and has_calculation
):
cache = subconfig.config_bag.context.get_values_cache()
cache.delcache(subconfig.path)
@ -428,36 +433,28 @@ class Values:
If not found, return None
For follower option, return the Config where leader is modified
"""
def build_option_bag(subconfig, parent):
doption_bag = subconfig.copy()
config_bag = subconfig.config_bag.copy()
config_bag.context = parent
config_bag.unrestraint()
doption_bag.config_bag = config_bag
return doption_bag
for parent in subconfig.config_bag.context.get_parents():
doption_bag = build_option_bag(subconfig, parent)
parent_subconfig = subconfig.change_context(parent)
parent_subconfig.config_bag.unrestraint()
parent_subconfig.properties = subconfig.properties
if "force_metaconfig_on_freeze" in subconfig.properties:
# remove force_metaconfig_on_freeze only if option in metaconfig
# hasn't force_metaconfig_on_freeze properties
ori_properties = doption_bag.properties
settings = doption_bag.config_bag.context.get_settings()
doption_bag.properties = settings.getproperties(doption_bag)
if not self.check_force_to_metaconfig(doption_bag):
doption_bag.properties = ori_properties - {
ori_properties = parent_subconfig.properties
settings = parent_subconfig.config_bag.context.get_settings()
parent_subconfig.properties = settings.getproperties(parent_subconfig)
if not self.check_force_to_metaconfig(parent_subconfig):
parent_subconfig.properties = ori_properties - {
"force_metaconfig_on_freeze"
}
else:
doption_bag.properties = ori_properties
parent_subconfig.properties = ori_properties
parent_owner = parent.get_values().getowner(
doption_bag,
parent,
parent_subconfig,
only_default=True,
)
if parent_owner != owners.default:
return doption_bag
return parent_subconfig
return None
@ -511,9 +508,6 @@ class Values:
was present
:returns: a `setting.owners.Owner` object
"""
# context = subconfig.config_bag.context
# settings = context.get_settings()
# settings.validate_properties(subconfig)
if (
"frozen" in subconfig.properties
and "force_default_on_freeze" in subconfig.properties
@ -542,11 +536,10 @@ class Values:
values = msubconfig.config_bag.context.get_values()
owner = values.getowner(
msubconfig,
parent,
only_default=only_default,
)
elif "force_metaconfig_on_freeze" in subconfig.properties:
return owners.default
owner = owners.default
return owner
def set_owner(
@ -587,35 +580,39 @@ class Values:
"""reset value for an option"""
config_bag = subconfig.config_bag
hasvalue = self.hasvalue(subconfig.path)
self_properties = subconfig.properties
context = config_bag.context
setting_properties = config_bag.properties
if validate:
if hasvalue and "validator" in setting_properties:
fake_context = context.gen_fake_context()
fake_config_bag = config_bag.copy()
fake_config_bag.remove_validation()
fake_config_bag.context = fake_context
fake_subconfig = fake_context.get_sub_config(
fake_config_bag,
subconfig.path,
subconfig.index,
validate_properties=False,
)
fake_values = fake_context.get_values()
fake_values.reset(fake_subconfig)
fake_subconfig.config_bag.properties = setting_properties
value = fake_values.get_default_value(fake_subconfig)
fake_values.setvalue_validation(
fake_subconfig,
value,
)
# if hasvalue:
if (
validate
and hasvalue
and "validator" in setting_properties
and "validator" in self_properties
):
fake_context = context.gen_fake_context()
fake_config_bag = config_bag.copy()
fake_config_bag.remove_validation()
fake_config_bag.context = fake_context
fake_subconfig = fake_context.get_sub_config(
fake_config_bag,
subconfig.path,
subconfig.index,
validate_properties=False,
)
fake_values = fake_context.get_values()
fake_values.reset(fake_subconfig)
fake_subconfig.config_bag.properties = setting_properties
value = fake_values.get_default_value(fake_subconfig)
fake_values.setvalue_validation(
fake_subconfig,
value,
)
opt = subconfig.option
if opt.impl_is_leader():
opt.impl_get_leadership().reset(subconfig.parent)
if (
"force_store_value" in setting_properties
and "force_store_value" in subconfig.properties
and "force_store_value" in self_properties
):
value = self.get_default_value(subconfig)
@ -662,10 +659,11 @@ class Values:
index=subconfig.index,
):
return
self_properties = subconfig.properties
config_bag = subconfig.config_bag
context = config_bag.context
setting_properties = config_bag.properties
if "validator" in setting_properties:
if "validator" in setting_properties and "validator" in self_properties:
fake_context = context.gen_fake_context()
fake_config_bag = config_bag.copy()
fake_config_bag.remove_validation()
@ -686,7 +684,7 @@ class Values:
)
if (
"force_store_value" in setting_properties
and "force_store_value" in subconfig.properties
and "force_store_value" in self_properties
):
value = self.get_default_value(
subconfig,
@ -738,8 +736,8 @@ class Values:
if index >= length:
raise IndexError(
_(
"index {index} is greater than the length {length} "
"for option {subconfig.option.impl_get_display_name(with_quote=True)}"
f"index {index} is greater than the length {length} "
f"for option {subconfig.option.impl_get_display_name(subconfig, with_quote=True)}"
)
)
current_value.pop(index)