Merge branch 'master' into orm

Conflicts:
	test/test_config_api.py
	tiramisu/autolib.py
	tiramisu/config.py
	tiramisu/option.py
	tiramisu/value.py
This commit is contained in:
Emmanuel Garette 2014-04-13 10:30:42 +02:00
commit 5d8b5719a0
47 changed files with 3683 additions and 3215 deletions

View File

@ -1,8 +1,8 @@
Tiramisu is placed under the terms of the GNU General Public License v3.0 as
Tiramisu is placed under the terms of the GNU LESSER GENERAL PUBLIC LICENSE V3 as
published by the Free Software Foundation; either version 3 of the
License, or (at your option) any later version.
See gpl-3.0.txt for more informations.
See lgpl-3.0.txt for more informations.
The documentation is licenced under the terms of a Creative Commons
Attribution-ShareAlike 3.0 Unported License.

13
ChangeLog Normal file
View File

@ -0,0 +1,13 @@
Sat Apr 12 11:37:27 CEST 2014 Emmanuel Garette <egarette@cadoles.com>
* behavior change in master/slave part of code:
if slave has a default value greater than master's one, it's raise
SlaveError, didn't try to reduce the slave's length
* tiramisu/config.py (in cfgimpl_get_home_by_path and getattr) and
tiramisu/value.py (in getitem): arity change, remove force_properties
* tiramisu/option.py: split into tiramisu/option directory
* tiramisu/option/masterslave.py: master/slaves have no a special
object MasterSlaves for all code related to master/slaves options
* tiramisu/option/masterslave.py: master and slaves values (length,
consistency, ...) are now check every time
* change None to undefined when needed

View File

@ -1 +1 @@
1.0rc1
master

View File

@ -49,9 +49,14 @@ Indices and tables
* :ref:`modindex`
* :ref:`search`
.. note:: This documentation is licensed under a `Creative Commons
.. note:: The tiramisu code is licensed under the `LGPL licence`_
and this documentation is licensed under the `Creative Commons
Attribution-ShareAlike 3.0 Unported License`_\ .
.. _`Creative Commons Attribution-ShareAlike 3.0 Unported License`: http://creativecommons.org/licenses/by-sa/3.0/deed.en_US
.. _`LGPL licence`: http://www.gnu.org/licenses/lgpl.html
.. todolist::

View File

@ -1,674 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

165
lgpl-3.0.txt Normal file
View File

@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

View File

@ -21,9 +21,9 @@ def return_storages():
storage_list = ['.'.join(storage.split('/')[-3:]) for storage in storages]
return storage_list
packages = ['tiramisu', 'tiramisu.storage']
packages.extend(return_storages())
setup(
author="Tiramisu's team",
author_email='contact@cadoles.com',
@ -38,8 +38,7 @@ setup(
"Development Status :: 4 - Beta",
"Environment :: Other Environment",
"Intended Audience :: Developers",
# "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
"License :: OSI Approved :: GNU General Public License (GPL)",
"License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
"Operating System :: OS Independent",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Text Processing :: Linguistic"

View File

@ -4,7 +4,11 @@ from tiramisu import setting
setting.expires_time = 1
from tiramisu.option import IntOption, OptionDescription
from tiramisu.config import Config
from tiramisu.error import ConfigError
from time import sleep, time
from py.test import raises
def make_description():
@ -253,3 +257,25 @@ def test_reset_cache_only():
c.cfgimpl_reset_cache(only=('settings',))
assert 'u1' in values._p_.get_cached(c)
assert 'u1' not in settings._p_.get_cached(c)
def test_force_cache():
u1 = IntOption('u1', '', multi=True)
u2 = IntOption('u2', '')
u3 = IntOption('u3', '', multi=True)
u4 = IntOption('u4', '', properties=('disabled',))
od = OptionDescription('od1', '', [u1, u2, u3, u4])
c = Config(od)
c.cfgimpl_get_settings().remove('expire')
c.cfgimpl_get_values().force_cache()
assert c.cfgimpl_get_values()._p_.get_cached(c) == {'u1': ([], None), 'u3': ([], None), 'u2': (None, None), 'u4': (None, None)}
assert c.cfgimpl_get_settings()._p_.get_cached(c) == {'u4': (set(['disabled']), None), 'u1': (set([]), None), 'u3': (set([]), None), 'u2': (set([]), None)}
c.read_only()
c.cfgimpl_get_values().force_cache()
assert c.cfgimpl_get_values()._p_.get_cached(c) == {'u1': ([], None), 'u3': ([], None), 'u2': (None, None)}
assert c.cfgimpl_get_settings()._p_.get_cached(c) == {'u4': (set(['disabled']), None), 'u1': (set([]), None), 'u3': (set([]), None), 'u2': (set([]), None)}
c.cfgimpl_get_settings().remove('cache')
raises(ConfigError, "c.cfgimpl_get_values().force_cache()")

View File

@ -9,7 +9,7 @@ from py.test import raises
from tiramisu.config import Config, SubConfig
from tiramisu.option import IntOption, FloatOption, StrOption, ChoiceOption, \
BoolOption, UnicodeOption, OptionDescription
from tiramisu.error import ConflictError, ConfigError
from tiramisu.error import ConflictError, ConfigError, PropertiesOptionError
import weakref
@ -22,7 +22,7 @@ def make_description():
intoption = IntOption('int', 'Test int option', default=0)
floatoption = FloatOption('float', 'Test float option', default=2.3)
stroption = StrOption('str', 'Test string option', default="abc", properties=('mandatory', ))
boolop = BoolOption('boolop', 'Test boolean option op', default=True)
boolop = BoolOption('boolop', 'Test boolean option op', default=True, properties=('hidden',))
wantref_option = BoolOption('wantref', 'Test requires', default=False)
wantframework_option = BoolOption('wantframework', 'Test requires',
default=False)
@ -90,6 +90,15 @@ def test_base_config_and_groups():
#assert nm.impl_getname() == 'name'
def test_base_config_force_permissive():
descr = make_description()
config = Config(descr)
config.read_write()
config.cfgimpl_get_settings().setpermissive(('hidden',))
raises(PropertiesOptionError, "config.getattr('boolop')")
assert config.getattr('boolop', force_permissive=True) is True
def test_base_config_in_a_tree():
"how options are organized into a tree, see :ref:`tree`"
descr = make_description()

View File

@ -102,9 +102,12 @@ def test_make_dict():
"serialization of the whole config to a dict"
descr = OptionDescription("opt", "", [
OptionDescription("s1", "", [
BoolOption("a", "", default=False)]),
BoolOption("a", "", default=False),
BoolOption("b", "", default=False, properties=('hidden',))]),
IntOption("int", "", default=42)])
config = Config(descr)
config.read_write()
config.cfgimpl_get_settings().setpermissive(('hidden',))
d = config.make_dict()
assert d == {"s1.a": False, "int": 42}
config.int = 43
@ -114,6 +117,20 @@ def test_make_dict():
d2 = config.make_dict(flatten=True)
assert d2 == {'a': True, 'int': 43}
raises(ValueError, 'd2 = config.make_dict(withvalue="3")')
d = config.make_dict(force_permissive=True)
assert d == {"s1.a": True, "s1.b": False, "int": 43}
def test_make_dict_with_disabled():
descr = OptionDescription("opt", "", [
OptionDescription("s1", "", [
BoolOption("a", "", default=False),
BoolOption("b", "", default=False, properties=('disabled',))]),
IntOption("int", "", default=42)])
config = Config(descr)
config.read_only()
d = config.make_dict()
assert d == {"s1.a": False, "int": 42}
def test_find_in_config():
@ -121,6 +138,7 @@ def test_find_in_config():
descr = make_description()
conf = Config(descr)
conf.read_only()
conf.cfgimpl_get_settings().setpermissive(('hidden',))
assert conf.find(byname='dummy') == [conf.unwrap_from_path('gc.dummy')]
assert conf.find(byname='float') == [conf.unwrap_from_path('gc.float'), conf.unwrap_from_path('float')]
assert conf.find_first(byname='bool') == conf.unwrap_from_path('gc.gc2.bool')
@ -135,6 +153,9 @@ def test_find_in_config():
conf.read_write()
raises(AttributeError, "assert conf.find(byname='prop')")
assert conf.find(byname='prop', check_properties=False) == [conf.unwrap_from_path('gc.gc2.prop'), conf.unwrap_from_path('gc.prop')]
assert conf.find(byname='prop', force_permissive=True) == [conf.unwrap_from_path('gc.prop')]
assert conf.find_first(byname='prop', force_permissive=True) == conf.unwrap_from_path('gc.prop')
#assert conf.find_first(byname='prop') == conf.unwrap_from_path('gc.prop')
# combinaison of filters
assert conf.find(bytype=BoolOption, byname='dummy') == [conf.unwrap_from_path('gc.dummy')]
assert conf.find_first(bytype=BoolOption, byname='dummy') == conf.unwrap_from_path('gc.dummy')
@ -205,6 +226,20 @@ def test_iter_all():
break
def test_iter_all_force_permissive():
s = StrOption("string", "", default="string")
s2 = StrOption("string2", "", default="string2")
s3 = StrOption("string3", "", default="string3", properties=('hidden',))
descr = OptionDescription("options", "", [s, s2, s3])
config = Config(descr)
config.read_write()
config.cfgimpl_get_settings().setpermissive(('hidden',))
assert list(config.iter_all()) == [('string', 'string'), ('string2', 'string2')]
assert list(config.iter_all(force_permissive=True)) == [('string', 'string'),
('string2', 'string2'),
('string3', 'string3')]
def test_iter_all_prop():
s = StrOption("string", "", default="string", properties=('disabled',))
s2 = StrOption("string2", "", default="string2")

View File

@ -16,7 +16,7 @@ def test_domainname():
raises(ValueError, "c.d = 'toto'")
c.d = 'toto3.com'
raises(ValueError, "c.d = 'toto3.3la'")
raises(ValueError, "c.d = '3toto.com'")
#raises(ValueError, "c.d = '3toto.com'")
raises(ValueError, "c.d = 'toto.co3'")
raises(ValueError, "c.d = 'toto_super.com'")
c.d = 'toto-.com'
@ -31,6 +31,19 @@ def test_domainname():
c.g = '192.168.1.0'
c.g = '192.168.1.29'
def test_special_domain_name():
"""domain name option that starts with a number or not
"""
d = DomainnameOption('d', '')
e = DomainnameOption('e', '', type_='netbios')
od = OptionDescription('a', '', [d,e])
c = Config(od)
c.read_write()
c.d = '1toto.com'
c.d = '123toto.com'
c.e = 'toto'
raises(ValueError, "c.e = '1toto'")
def test_domainname_netbios():
d = DomainnameOption('d', '', type_='netbios')

View File

@ -22,13 +22,14 @@ def make_description_freeze():
boolop = BoolOption('boolop', 'Test boolean option op', default=[True], multi=True)
wantref_option = BoolOption('wantref', 'Test requires', default=False, properties=('force_store_value',),
requires=({'option': booloption, 'expected': True, 'action': 'hidden'},))
wantref2_option = BoolOption('wantref2', 'Test requires', default=False, properties=('force_store_value', 'hidden'))
wantframework_option = BoolOption('wantframework', 'Test requires',
default=False,
requires=({'option': booloption, 'expected': True, 'action': 'hidden'},))
gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, floatoption])
descr = OptionDescription('tiramisu', '', [gcgroup, booloption, objspaceoption,
wantref_option, stroption,
wantref_option, wantref2_option, stroption,
wantframework_option,
intoption, boolop])
return descr
@ -141,15 +142,88 @@ def test_freeze_get_multi():
def test_force_store_value():
descr = make_description_freeze()
conf = Config(descr)
assert conf.getowner(conf.unwrap_from_path('wantref')) == 'default'
assert conf.cfgimpl_get_values()._p_.get_modified_values() == {}
conf.wantref
assert conf.getowner(conf.unwrap_from_path('wantref')) == 'user'
assert conf.cfgimpl_get_values()._p_.get_modified_values() == {'wantref': ('user', False)}
def test_force_store_value_no_requirement():
booloption = BoolOption('bool', 'Test boolean option', default=True)
try:
BoolOption('wantref', 'Test requires', default=False,
requires=({'option': booloption, 'expected': True, 'action': 'force_store_value'},))
except ValueError:
pass
def test_force_store_value_ro():
descr = make_description_freeze()
conf = Config(descr)
conf.read_only()
assert conf.getowner(conf.unwrap_from_path('wantref')) == 'default'
assert conf.cfgimpl_get_values()._p_.get_modified_values() == {}
conf.wantref
assert conf.getowner(conf.unwrap_from_path('wantref')) == 'user'
assert conf.cfgimpl_get_values()._p_.get_modified_values() == {'wantref': ('user', False)}
def test_force_store_value_hidden():
descr = make_description_freeze()
conf = Config(descr)
conf.cfgimpl_get_settings().setpermissive(('hidden',))
conf.read_write()
assert conf.cfgimpl_get_values()._p_.get_modified_values() == {}
conf.getattr('wantref2', force_permissive=True)
assert conf.cfgimpl_get_values()._p_.get_modified_values() == {'wantref2': ('user', False)}
def test_force_store_value_owner():
descr = make_description_freeze()
conf = Config(descr)
assert conf.cfgimpl_get_values()._p_.get_modified_values() == {}
conf.getowner(conf.unwrap_from_path('wantref'))
assert conf.cfgimpl_get_values()._p_.get_modified_values() == {'wantref': ('user', False)}
def test_force_store_value_owner_ro():
descr = make_description_freeze()
conf = Config(descr)
conf.read_only()
assert conf.cfgimpl_get_values()._p_.get_modified_values() == {}
conf.getowner(conf.unwrap_from_path('wantref'))
assert conf.cfgimpl_get_values()._p_.get_modified_values() == {'wantref': ('user', False)}
def test_force_store_value_owner_hidden():
descr = make_description_freeze()
conf = Config(descr)
conf.cfgimpl_get_settings().setpermissive(('hidden',))
conf.read_write()
assert conf.cfgimpl_get_values()._p_.get_modified_values() == {}
conf.getowner(conf.unwrap_from_path('wantref2'), force_permissive=True)
assert conf.cfgimpl_get_values()._p_.get_modified_values() == {'wantref2': ('user', False)}
def test_force_store_value_modified():
descr = make_description_freeze()
conf = Config(descr)
assert conf.cfgimpl_get_values()._p_.get_modified_values() == {}
conf.cfgimpl_get_values().get_modified_values()
assert conf.cfgimpl_get_values()._p_.get_modified_values() == {'wantref': ('user', False), 'wantref2': ('user', False)}
def test_force_store_value_modified_ro():
descr = make_description_freeze()
conf = Config(descr)
conf.read_only()
assert conf.cfgimpl_get_values()._p_.get_modified_values() == {}
conf.cfgimpl_get_values().get_modified_values()
assert conf.cfgimpl_get_values()._p_.get_modified_values() == {'wantref': ('user', False), 'wantref2': ('user', False)}
def test_force_store_value_modified_hidden():
descr = make_description_freeze()
conf = Config(descr)
conf.cfgimpl_get_settings().setpermissive(('hidden',))
conf.read_write()
assert conf.cfgimpl_get_values()._p_.get_modified_values() == {}
conf.cfgimpl_get_values().get_modified_values()
assert conf.cfgimpl_get_values()._p_.get_modified_values() == {'wantref': ('user', False), 'wantref2': ('user', False)}

View File

@ -1,7 +1,8 @@
import autopath
from time import sleep
#from py.test import raises
from tiramisu.config import Config, mandatory_warnings
from tiramisu.config import Config
from tiramisu.option import StrOption, UnicodeOption, OptionDescription
from tiramisu.error import PropertiesOptionError
@ -99,8 +100,8 @@ def test_mandatory_multi_none():
descr = make_description()
config = Config(descr)
config.str3 = [None]
config.read_only()
assert config.getowner(config.unwrap_from_path('str3')) == 'user'
config.read_only()
prop = []
try:
config.str3
@ -109,8 +110,8 @@ def test_mandatory_multi_none():
assert 'mandatory' in prop
config.read_write()
config.str3 = ['yes', None]
config.read_only()
assert config.getowner(config.unwrap_from_path('str3')) == 'user'
config.read_only()
prop = []
try:
config.str3
@ -123,8 +124,8 @@ def test_mandatory_multi_empty():
descr = make_description()
config = Config(descr)
config.str3 = ['']
config.read_only()
assert config.getowner(config.unwrap_from_path('str3')) == 'user'
config.read_only()
prop = []
try:
config.str3
@ -133,8 +134,8 @@ def test_mandatory_multi_empty():
assert 'mandatory' in prop
config.read_write()
config.str3 = ['yes', '']
config.read_only()
assert config.getowner(config.unwrap_from_path('str3')) == 'user'
config.read_only()
prop = []
try:
config.str3
@ -205,11 +206,12 @@ def test_mandatory_warnings_ro():
except PropertiesOptionError as err:
proc = err.proptype
assert proc == ['mandatory']
assert list(mandatory_warnings(config)) == ['str', 'str1', 'unicode2', 'str3']
assert config.cfgimpl_get_values().mandatory_warnings() == ['str', 'str1', 'unicode2', 'str3']
config.read_write()
config.str = 'a'
config.read_only()
assert list(mandatory_warnings(config)) == ['str1', 'unicode2', 'str3']
assert config.cfgimpl_get_values().mandatory_warnings() == ['str1', 'unicode2', 'str3']
sleep(.1)
def test_mandatory_warnings_rw():
@ -218,9 +220,10 @@ def test_mandatory_warnings_rw():
config.str = ''
config.read_write()
config.str
assert list(mandatory_warnings(config)) == ['str', 'str1', 'unicode2', 'str3']
assert config.cfgimpl_get_values().mandatory_warnings() == ['str', 'str1', 'unicode2', 'str3']
config.str = 'a'
assert list(mandatory_warnings(config)) == ['str1', 'unicode2', 'str3']
assert config.cfgimpl_get_values().mandatory_warnings() == ['str1', 'unicode2', 'str3']
sleep(.1)
def test_mandatory_warnings_disabled():
@ -230,9 +233,10 @@ def test_mandatory_warnings_disabled():
setting = config.cfgimpl_get_settings()
config.read_write()
config.str
assert list(mandatory_warnings(config)) == ['str', 'str1', 'unicode2', 'str3']
assert config.cfgimpl_get_values().mandatory_warnings() == ['str', 'str1', 'unicode2', 'str3']
setting[descr.str].append('disabled')
assert list(mandatory_warnings(config)) == ['str1', 'unicode2', 'str3']
assert config.cfgimpl_get_values().mandatory_warnings() == ['str1', 'unicode2', 'str3']
sleep(.1)
def test_mandatory_warnings_frozen():
@ -242,7 +246,8 @@ def test_mandatory_warnings_frozen():
setting = config.cfgimpl_get_settings()
config.read_write()
config.str
assert list(mandatory_warnings(config)) == ['str', 'str1', 'unicode2', 'str3']
assert config.cfgimpl_get_values().mandatory_warnings() == ['str', 'str1', 'unicode2', 'str3']
setting[descr.str].append('frozen')
config.read_only()
assert list(mandatory_warnings(config)) == ['str', 'str1', 'unicode2', 'str3']
assert config.cfgimpl_get_values().mandatory_warnings() == ['str', 'str1', 'unicode2', 'str3']
sleep(.1)

View File

@ -1,8 +1,8 @@
import autopath
from py.test import raises
from tiramisu.setting import groups
from tiramisu.config import Config
from tiramisu.setting import groups, owners
from tiramisu.option import ChoiceOption, BoolOption, IntOption, FloatOption, \
StrOption, OptionDescription, SymLinkOption
from tiramisu.error import PropertiesOptionError, ConflictError, SlaveError, ConfigError
@ -44,6 +44,13 @@ def return_calc(i, j, k):
return i + j + k
def is_config(config, **kwargs):
if isinstance(config, Config):
return 'yes'
else:
return 'no'
def make_description():
gcoption = ChoiceOption('name', 'GC name', ('ref', 'framework'), 'ref')
gcdummy = BoolOption('dummy', 'dummy', default=False)
@ -254,6 +261,29 @@ def test_callback_invalid():
raises(ValueError, "StrOption('val2', '', callback=return_value, callback_params={'': 'string'})")
raises(ValueError, "StrOption('val4', '', callback=return_value, callback_params={'value': (('string', False),)})")
raises(ValueError, "StrOption('val4', '', callback=return_value, callback_params={'value': ((val1, 'string'),)})")
raises(ValueError, "StrOption('val4', '', callback=return_value, callback_params={'value': ((val1, False, 'unknown'),)})")
raises(ValueError, "StrOption('val4', '', callback=return_value, callback_params={'value': ((val1,),)})")
def test_callback_with_context():
val1 = StrOption("val1", "", callback=is_config, callback_params={'': ((None,),), 'value': ('string',)})
maconfig = OptionDescription('rootconfig', '', [val1])
cfg = Config(maconfig)
assert cfg.val1 == 'yes'
def test_callback_with_context_named():
val1 = StrOption("val1", "", callback=is_config, callback_params={'config': ((None,),)})
maconfig = OptionDescription('rootconfig', '', [val1])
cfg = Config(maconfig)
assert cfg.val1 == 'yes'
def test_callback_with_error():
val1 = StrOption("val1", "", callback=is_config, callback_params={'': ('string',), 'value': ('string',)})
maconfig = OptionDescription('rootconfig', '', [val1])
cfg = Config(maconfig)
assert cfg.val1 == 'no'
def test_callback_value():
@ -401,6 +431,17 @@ def test_callback_multi_list_extend():
assert cfg.val1 == ['1', '2', '3', '4', '5']
def test_callback_multi_callback():
val1 = StrOption('val1', "", multi=True, callback=return_val)
interface1 = OptionDescription('val1', '', [val1])
maconfig = OptionDescription('rootconfig', '', [interface1])
cfg = Config(maconfig)
cfg.read_write()
assert cfg.val1.val1 == ['val']
cfg.val1.val1.append()
assert cfg.val1.val1 == ['val', 'val']
def test_callback_master_and_slaves_master():
val1 = StrOption('val1', "", multi=True, callback=return_val)
val2 = StrOption('val2', "", multi=True)
@ -415,6 +456,22 @@ def test_callback_master_and_slaves_master():
assert cfg.val1.val2 == [None, None]
def test_callback_master_and_slaves_master2():
val1 = StrOption('val1', "", multi=True)
val2 = StrOption('val2', "", multi=True, default_multi='val2')
val3 = StrOption('val3', "", multi=True, callback=return_value, callback_params={'': ((val2, False),)})
val4 = StrOption('val4', "", multi=True, callback=return_value, callback_params={'': ((val3, False),)})
interface1 = OptionDescription('val1', '', [val1, val2, val3, val4])
interface1.impl_set_group_type(groups.master)
maconfig = OptionDescription('rootconfig', '', [interface1])
cfg = Config(maconfig)
cfg.read_write()
cfg.val1.val1.append('val')
assert cfg.val1.val4 == ['val2']
assert cfg.val1.val3 == ['val2']
assert cfg.val1.val2 == ['val2']
def test_callback_master_and_slaves_master_list():
val1 = StrOption('val1', "", multi=True, callback=return_list)
val2 = StrOption('val2', "", multi=True)
@ -470,6 +527,16 @@ def test_callback_master_and_slaves_slave():
assert cfg.val1.val2 == ['val2', 'val2', 'val']
def test_callback_master_and_slaves():
val1 = StrOption('val1', "", multi=True)
val2 = StrOption('val2', "", multi=True, callback=return_val)
interface1 = OptionDescription('val1', '', [val1, val2])
interface1.impl_set_group_type(groups.master)
maconfig = OptionDescription('rootconfig', '', [interface1])
cfg = Config(maconfig)
cfg.read_write()
def test_callback_master_and_slaves_slave_cal():
val3 = StrOption('val3', "", multi=True)
val1 = StrOption('val1', "", multi=True, callback=return_value, callback_params={'': ((val3, False),)})
@ -486,8 +553,6 @@ def test_callback_master_and_slaves_slave_cal():
cfg.val3 = ['val1']
assert cfg.val1.val1 == ['val1']
assert cfg.val1.val2 == ['val']
assert cfg.val1.val1 == ['val1']
assert cfg.val1.val2 == ['val']
del(cfg.val1.val1)
cfg.val1.val2 = ['val']
cfg.val3 = ['val1', 'val2']
@ -516,8 +581,8 @@ def test_callback_master_and_slaves_slave_cal2():
assert cfg.val1.val1 == ['val', 'val']
assert cfg.val1.val2 == ['val2', 'val2']
cfg.val3.pop(1)
# # cannot remove slave's value because master is calculated
# # so raise
# cannot remove slave's value because master is calculated
# so raise
raises(SlaveError, "cfg.val1.val1")
raises(SlaveError, "cfg.val1.val2")
cfg.val3 = ['val', 'val']
@ -530,6 +595,88 @@ def test_callback_master_and_slaves_slave_cal2():
assert cfg.val1.val2 == ['val2', 'val2']
def test_callback_master_and_slaves_master_disabled():
#properties must be transitive
val1 = StrOption('val1', "", multi=True, properties=('disabled',))
val2 = StrOption('val2', "", multi=True)
interface1 = OptionDescription('val1', '', [val1, val2])
interface1.impl_set_group_type(groups.master)
maconfig = OptionDescription('rootconfig', '', [interface1])
cfg = Config(maconfig)
cfg.read_write()
raises(PropertiesOptionError, "cfg.val1.val1")
raises(PropertiesOptionError, "cfg.val1.val1.append('yes')")
raises(PropertiesOptionError, "cfg.val1.val2")
def test_callback_master_and_slaves_master_callback_disabled():
val0 = StrOption('val0', "", multi=True, properties=('disabled',))
val1 = StrOption('val1', "", multi=True, callback=return_value, callback_params={'': ((val0, False),)})
val2 = StrOption('val2', "", multi=True)
interface1 = OptionDescription('val1', '', [val1, val2])
interface1.impl_set_group_type(groups.master)
maconfig = OptionDescription('rootconfig', '', [interface1, val0])
cfg = Config(maconfig)
cfg.read_write()
raises(ConfigError, "cfg.val1.val1")
raises(ConfigError, "cfg.val1.val2")
cfg.cfgimpl_get_settings().remove('disabled')
cfg.val1.val1 = []
cfg.cfgimpl_get_settings().append('disabled')
assert cfg.val1.val1 == []
assert cfg.val1.val2 == []
def test_callback_master_and_slaves_slave_disabled():
val1 = StrOption('val1', "", multi=True)
val2 = StrOption('val2', "", multi=True, properties=('disabled',))
interface1 = OptionDescription('val1', '', [val1, val2])
interface1.impl_set_group_type(groups.master)
maconfig = OptionDescription('rootconfig', '', [interface1])
cfg = Config(maconfig)
cfg.read_write()
assert cfg.val1.val1 == []
raises(PropertiesOptionError, "cfg.val1.val2")
cfg.val1.val1.append('yes')
assert cfg.val1.val1 == ['yes']
cfg.cfgimpl_get_settings().remove('disabled')
assert cfg.val1.val2 == [None]
cfg.val1.val2 = ['no']
cfg.val1.val1.append('yes2')
cfg.val1.val1.append('yes3')
cfg.val1.val2[2] = 'no1'
assert cfg.val1.val2 == ['no', None, 'no1']
cfg.cfgimpl_get_settings().append('disabled')
cfg.val1.val1.pop(0)
assert cfg.val1.val1 == ['yes2', 'yes3']
cfg.cfgimpl_get_settings().remove('disabled')
assert cfg.val1.val2 == [None, 'no1']
def test_callback_master_and_slaves_slave_callback_disabled():
val0 = StrOption('val0', "", multi=True, properties=('disabled',))
val1 = StrOption('val1', "", multi=True)
val2 = StrOption('val2', "", multi=True, callback=return_value, callback_params={'': ((val0, False),)})
interface1 = OptionDescription('val1', '', [val1, val2])
interface1.impl_set_group_type(groups.master)
maconfig = OptionDescription('rootconfig', '', [interface1, val0])
cfg = Config(maconfig)
cfg.read_write()
assert cfg.val1.val1 == []
raises(ConfigError, "cfg.val1.val2")
cfg.val1.val1.append('yes')
assert cfg.val1.val1 == ['yes']
cfg.cfgimpl_get_settings().remove('disabled')
assert cfg.val1.val2 == [None]
cfg.val1.val2 = ['no']
cfg.val1.val1.append('yes1')
cfg.val1.val2[1] = 'no1'
cfg.cfgimpl_get_settings().append('disabled')
cfg.val1.val1.pop(0)
assert cfg.val1.val1 == ['yes1']
assert cfg.val1.val2 == ['no1']
def test_callback_master_and_slaves_slave_list():
val1 = StrOption('val1', "", multi=True)
val2 = StrOption('val2', "", multi=True, callback=return_list)
@ -538,20 +685,20 @@ def test_callback_master_and_slaves_slave_list():
maconfig = OptionDescription('rootconfig', '', [interface1])
cfg = Config(maconfig)
cfg.read_write()
assert cfg.val1.val2 == []
#len is equal to 2 for slave and 0 for master
raises(SlaveError, "cfg.val1.val2")
cfg.val1.val1 = ['val1', 'val2']
assert cfg.val1.val1 == ['val1', 'val2']
assert cfg.val1.val2 == ['val', 'val']
cfg.val1.val1 = ['val1']
#wrong len
raises(SlaveError, 'cfg.val1.val2')
raises(SlaveError, "cfg.val1.val1 = ['val1']")
def test_callback_master_and_slaves_value():
val4 = StrOption('val4', '', multi=True, default=['val10', 'val11'])
val1 = StrOption('val1', "", multi=True)
val2 = StrOption('val2', "", multi=True, callback=return_value, callback_params={'': ((val1, False),)})
val3 = StrOption('val3', "", multi=True, callback=return_value, callback_params={'': ('yes',)})
val4 = StrOption('val4', '', multi=True, default=['val10', 'val11'])
val5 = StrOption('val5', "", multi=True, callback=return_value, callback_params={'': ((val4, False),)})
val6 = StrOption('val6', "", multi=True, callback=return_value, callback_params={'': ((val5, False),)})
interface1 = OptionDescription('val1', '', [val1, val2, val3, val5, val6])
@ -559,27 +706,24 @@ def test_callback_master_and_slaves_value():
maconfig = OptionDescription('rootconfig', '', [interface1, val4])
cfg = Config(maconfig)
cfg.read_write()
assert cfg.val1.val1 == []
assert cfg.val1.val2 == []
assert cfg.val1.val3 == []
assert cfg.val1.val5 == []
assert cfg.val1.val6 == []
cfg.val4 == ['val10', 'val11']
raises(SlaveError, "cfg.val1.val1")
raises(SlaveError, "cfg.val1.val2")
raises(SlaveError, "cfg.val1.val3")
raises(SlaveError, "cfg.val1.val5")
raises(SlaveError, "cfg.val1.val6")
#
cfg.val1.val1 = ['val1']
assert cfg.val1.val1 == ['val1']
assert cfg.val1.val2 == ['val1']
assert cfg.val1.val3 == ['yes']
assert cfg.val1.val5 == ['val10']
assert cfg.val1.val6 == ['val10']
#default calculation has greater length
raises(SlaveError, "cfg.val1.val1 = ['val1']")
#
cfg.val1.val1.append('val2')
cfg.val1.val1 = ['val1', 'val2']
assert cfg.val1.val1 == ['val1', 'val2']
assert cfg.val1.val2 == ['val1', 'val2']
assert cfg.val1.val3 == ['yes', 'yes']
assert cfg.val1.val5 == ['val10', 'val11']
assert cfg.val1.val6 == ['val10', 'val11']
#
cfg.val1.val1 = ['val1', 'val2', 'val3']
cfg.val1.val1.append('val3')
assert cfg.val1.val1 == ['val1', 'val2', 'val3']
assert cfg.val1.val2 == ['val1', 'val2', 'val3']
assert cfg.val1.val3 == ['yes', 'yes', 'yes']
@ -650,9 +794,16 @@ def test_callback_master_and_other_master_slave():
assert cfg.val4.val6 == ['no', None]
cfg.val1.val1 = ['yes', 'yes', 'yes']
cfg.val1.val2 = ['no', 'no', 'no']
assert cfg.val4.val4 == ['val10', 'val11']
assert cfg.val4.val5 == ['yes', 'yes']
assert cfg.val4.val6 == ['no', 'no']
raises(SlaveError, "cfg.val4.val4")
raises(SlaveError, "cfg.val4.val5")
raises(SlaveError, "cfg.val4.val6")
cfg.val4.getattr('val4', validate=False).append('val12')
assert cfg.val4.val4 == ['val10', 'val11', 'val12']
assert cfg.val4.val5 == ['yes', 'yes', 'yes']
assert cfg.val4.val6 == ['no', 'no', 'no']
#FIXME: slave est un symlink
def test_callback_different_type():
@ -702,6 +853,19 @@ def test_callback_two_disabled():
raises(PropertiesOptionError, 'cfg.od2.opt2')
def test_callback_two_disabled2():
opt1 = BoolOption('opt1', '', properties=('hidden',))
opt2 = BoolOption('opt2', '', callback=return_value, callback_params={'': ((opt1, False),)}, properties=('hidden',))
od1 = OptionDescription('od1', '', [opt1])
od2 = OptionDescription('od2', '', [opt2])
maconfig = OptionDescription('rootconfig', '', [od1, od2])
cfg = Config(maconfig)
cfg.read_write()
cfg.cfgimpl_get_settings().setpermissive(('hidden',))
raises(PropertiesOptionError, 'cfg.od2.opt2')
assert cfg.getowner(opt2, force_permissive=True) == owners.default
def test_callback_calculating_disabled():
opt1 = BoolOption('opt1', '', properties=('disabled',))
opt2 = BoolOption('opt2', '', callback=return_value, callback_params={'': ((opt1, False),)})
@ -724,6 +888,17 @@ def test_callback_calculating_mandatory():
raises(ConfigError, 'cfg.od2.opt2')
def test_callback_calculating_mandatory_multi():
opt1 = BoolOption('opt1', '', multi=True, properties=('disabled',))
opt2 = BoolOption('opt2', '', multi=True, callback=return_value, callback_params={'': ((opt1, False),)}, properties=('mandatory',))
od1 = OptionDescription('od1', '', [opt1])
od2 = OptionDescription('od2', '', [opt2])
maconfig = OptionDescription('rootconfig', '', [od1, od2])
cfg = Config(maconfig)
cfg.read_only()
raises(ConfigError, 'cfg.od2.opt2')
def test_callback_two_disabled_multi():
opt1 = BoolOption('opt1', '', properties=('disabled',))
opt2 = BoolOption('opt2', '', callback=return_value, callback_params={'': ((opt1, False),)}, properties=('disabled',), multi=True)

View File

@ -5,7 +5,8 @@ from tiramisu.setting import owners, groups
from tiramisu.config import Config
from tiramisu.option import IPOption, NetworkOption, NetmaskOption, IntOption,\
BroadcastOption, SymLinkOption, OptionDescription
from tiramisu.error import ConfigError
from tiramisu.error import ConfigError, ValueWarning
import warnings
def test_consistency():
@ -19,6 +20,19 @@ def test_consistency():
raises(ConfigError, "a.impl_add_consistency('not_equal', 'a')")
def test_consistency_warnings_only():
a = IntOption('a', '')
b = IntOption('b', '')
od = OptionDescription('od', '', [a, b])
a.impl_add_consistency('not_equal', b, warnings_only=True)
c = Config(od)
c.a = 1
warnings.simplefilter("always", ValueWarning)
with warnings.catch_warnings(record=True) as w:
c.b = 1
assert w != []
def test_consistency_not_equal():
a = IntOption('a', '')
b = IntOption('b', '')
@ -186,6 +200,29 @@ def test_consistency_network_netmask():
raises(ValueError, "c.a = '192.168.1.1'")
def test_consistency_ip_in_network():
a = NetworkOption('a', '')
b = NetmaskOption('b', '')
c = IPOption('c', '')
od = OptionDescription('od', '', [a, b, c])
c.impl_add_consistency('in_network', a, b)
cfg = Config(od)
cfg.a = '192.168.1.0'
cfg.b = '255.255.255.0'
cfg.c = '192.168.1.1'
raises(ValueError, "cfg.c = '192.168.2.1'")
def test_consistency_ip_in_network_len_error():
a = NetworkOption('a', '')
b = NetmaskOption('b', '')
c = IPOption('c', '')
od = OptionDescription('od', '', [a, b, c])
c.impl_add_consistency('in_network', a)
cfg = Config(od)
raises(ConfigError, "cfg.a = '192.168.2.0'")
def test_consistency_ip_netmask_network_error():
a = IPOption('a', '')
b = NetworkOption('b', '')
@ -301,7 +338,7 @@ def test_consistency_broadcast_error():
c.impl_add_consistency('broadcast', a)
c = Config(od)
c.a = ['192.168.1.0']
c.b = ['255.255.255.0']
raises(ConfigError, "c.b = ['255.255.255.0']")
raises(ConfigError, "c.c = ['192.168.1.255']")
@ -346,3 +383,17 @@ def test_consistency_permissive():
c.cfgimpl_get_settings().setpermissive(('hidden',))
c.read_write()
c.a = 1
def return_val(*args, **kwargs):
return '192.168.1.1'
def test_consistency_with_callback():
a = NetworkOption('a', '', default='192.168.1.0')
b = NetmaskOption('b', '', default='255.255.255.0')
c = IPOption('c', '', callback=return_val, callback_params={'': ((a, False),)})
od = OptionDescription('od', '', [a, b, c])
c.impl_add_consistency('in_network', a, b)
cfg = Config(od)
cfg.c

View File

@ -5,7 +5,7 @@ from tiramisu.setting import owners
from tiramisu.config import Config
from tiramisu.option import ChoiceOption, BoolOption, IntOption, FloatOption, \
StrOption, OptionDescription
from tiramisu.error import ConfigError, ConstError
from tiramisu.error import ConfigError, ConstError, PropertiesOptionError
def make_description():
@ -41,6 +41,14 @@ def test_default_owner():
assert cfg.getowner(gcdummy) == owners.user
def test_hidden_owner():
gcdummy = BoolOption('dummy', 'dummy', default=False, properties=('hidden',))
descr = OptionDescription('tiramisu', '', [gcdummy])
cfg = Config(descr)
cfg.read_write()
raises(PropertiesOptionError, "cfg.getowner(gcdummy)")
def test_addowner():
gcdummy = BoolOption('dummy', 'dummy', default=False)
descr = OptionDescription('tiramisu', '', [gcdummy])

View File

@ -306,6 +306,15 @@ def test_access_by_get_whith_hide():
raises(AttributeError, "c.find(byname='b1')")
def test_extend_config_properties():
descr = make_description()
cfg = Config(descr)
setting = cfg.cfgimpl_get_settings()
assert str(setting) == str(['cache', 'expire', 'validator'])
setting.extend(['test', 'test2'])
assert str(setting) == str(['test', 'cache', 'test2', 'expire', 'validator'])
def test_append_properties():
descr = make_description()
cfg = Config(descr)
@ -363,3 +372,14 @@ def test_reset_multiple():
setting[option].append('test')
setting.reset(all_properties=True)
setting.reset(all_properties=True)
def test_properties_cached():
b1 = BoolOption("b1", "", properties=('test',))
descr = OptionDescription("opt", "", [OptionDescription("sub", "", [b1])])
c = Config(descr)
c.read_write()
setting = c.cfgimpl_get_settings()
option = c.cfgimpl_get_description().sub.b1
c._setattr('sub.b1', True, force_permissive=True)
assert str(setting[b1]) == "['test']"

View File

@ -0,0 +1,25 @@
"configuration objects global API"
import autopath
from py.test import raises
from tiramisu.config import Config
from tiramisu.option import UsernameOption
def test_username():
UsernameOption('a', '', 'string')
UsernameOption('a', '', '_string')
UsernameOption('a', '', 's_tring')
UsernameOption('a', '', 'string_')
UsernameOption('a', '', 'string$')
UsernameOption('a', '', '_string$')
raises(ValueError, "UsernameOption('a', '', 'strin$g')")
UsernameOption('a', '', 's-tring')
raises(ValueError, "UsernameOption('a', '', '-string')")
UsernameOption('a', '', 's9tring')
raises(ValueError, "UsernameOption('a', '', '9string')")
raises(ValueError, "UsernameOption('a', '', '')")
UsernameOption('a', '', 's')
UsernameOption('a', '', 's2345678901234567890123456789012')
raises(ValueError, "UsernameOption('a', '', 's23456789012345678901234567890123')")
UsernameOption('a', '', 's234567890123456789012345678901$')
raises(ValueError, "UsernameOption('a', '', 's2345678901234567890123456789012$')")

View File

@ -90,7 +90,7 @@ def test_validator_warning():
cfg.opt2 = 'val'
assert len(w) == 1
assert w[0].message.opt == opt2
assert str(w[0].message) == _('invalid value for option {0}: {1}').format('opt2', 'error')
assert str(w[0].message) == _("warning on the value of the option {0}: {1}").format('opt2', 'error')
#
with warnings.catch_warnings(record=True) as w:
cfg.opt3.append('val')
@ -100,7 +100,7 @@ def test_validator_warning():
cfg.opt3.append('val1')
assert len(w) == 1
assert w[0].message.opt == opt3
assert str(w[0].message) == _('invalid value for option {0}: {1}').format('opt3', 'error')
assert str(w[0].message) == _("warning on the value of the option {0}: {1}").format('opt3', 'error')
raises(ValueError, "cfg.opt2 = 1")
#
with warnings.catch_warnings(record=True) as w:
@ -108,9 +108,9 @@ def test_validator_warning():
cfg.opt3.append('val')
assert len(w) == 2
assert w[0].message.opt == opt2
assert str(w[0].message) == _('invalid value for option {0}: {1}').format('opt2', 'error')
assert str(w[0].message) == _("warning on the value of the option {0}: {1}").format('opt2', 'error')
assert w[1].message.opt == opt3
assert str(w[1].message) == _('invalid value for option {0}: {1}').format('opt3', 'error')
assert str(w[1].message) == _("warning on the value of the option {0}: {1}").format('opt3', 'error')
def test_validator_warning_master_slave():
@ -130,29 +130,29 @@ def test_validator_warning_master_slave():
cfg.ip_admin_eth0.netmask_admin_eth0 = ['val1']
assert len(w) == 1
assert w[0].message.opt == netmask_admin_eth0
assert str(w[0].message) == _('invalid value for option {0}: {1}').format('netmask_admin_eth0', 'error')
assert str(w[0].message) == _("warning on the value of the option {0}: {1}").format('netmask_admin_eth0', 'error')
#
with warnings.catch_warnings(record=True) as w:
cfg.ip_admin_eth0.ip_admin_eth0 = ['val']
assert len(w) == 1
assert w[0].message.opt == ip_admin_eth0
assert str(w[0].message) == _('invalid value for option {0}: {1}').format('ip_admin_eth0', 'error')
assert str(w[0].message) == _("warning on the value of the option {0}: {1}").format('ip_admin_eth0', 'error')
#
with warnings.catch_warnings(record=True) as w:
cfg.ip_admin_eth0.ip_admin_eth0 = ['val', 'val1', 'val1']
assert len(w) == 1
assert w[0].message.opt == ip_admin_eth0
assert str(w[0].message) == _('invalid value for option {0}: {1}').format('ip_admin_eth0', 'error')
assert str(w[0].message) == _("warning on the value of the option {0}: {1}").format('ip_admin_eth0', 'error')
#
with warnings.catch_warnings(record=True) as w:
cfg.ip_admin_eth0.ip_admin_eth0 = ['val1', 'val', 'val1']
assert len(w) == 1
assert w[0].message.opt == ip_admin_eth0
assert str(w[0].message) == _('invalid value for option {0}: {1}').format('ip_admin_eth0', 'error')
assert str(w[0].message) == _("warning on the value of the option {0}: {1}").format('ip_admin_eth0', 'error')
#
warnings.resetwarnings()
with warnings.catch_warnings(record=True) as w:
cfg.ip_admin_eth0.ip_admin_eth0 = ['val1', 'val1', 'val']
assert len(w) == 1
assert w[0].message.opt == ip_admin_eth0
assert str(w[0].message) == _('invalid value for option {0}: {1}').format('ip_admin_eth0', 'error')
assert str(w[0].message) == _("warning on the value of the option {0}: {1}").format('ip_admin_eth0', 'error')

View File

@ -34,7 +34,9 @@ def make_description():
mode_conteneur_actif, adresse_serveur_ntp,
time_zone])
general.impl_set_group_type(groups.family)
creole = OptionDescription('creole', 'first tiramisu configuration', [general, interface1])
new = OptionDescription('new', '', [], properties=('hidden',))
new.impl_set_group_type(groups.family)
creole = OptionDescription('creole', 'first tiramisu configuration', [general, interface1, new])
descr = OptionDescription('baseconfig', 'baseconifgdescr', [creole])
return descr
@ -99,6 +101,17 @@ def test_iter_on_groups():
break
def test_iter_on_groups_force_permissive():
descr = make_description()
config = Config(descr)
config.read_write()
config.cfgimpl_get_settings().setpermissive(('hidden',))
result = list(config.creole.iter_groups(group_type=groups.family,
force_permissive=True))
group_names = [res[0] for res in result]
assert group_names == ['general', 'interface1', 'new']
def test_iter_on_groups_props():
descr = make_description()
config = Config(descr)

View File

@ -242,6 +242,30 @@ def test_requires_transitive():
assert props == ['disabled']
def test_requires_transitive_owner():
a = BoolOption('activate_service', '', True)
b = BoolOption('activate_service_web', '', True,
requires=[{'option': a, 'expected': False, 'action': 'disabled'}])
d = IPOption('ip_address_service_web', '',
requires=[{'option': b, 'expected': False, 'action': 'disabled'}])
od = OptionDescription('service', '', [a, b, d])
c = Config(od)
c.read_write()
c.activate_service
c.activate_service_web
c.ip_address_service_web
#no more default value
c.ip_address_service_web = '1.1.1.1'
c.activate_service = False
props = []
try:
c.ip_address_service_web
except PropertiesOptionError as err:
props = err.proptype
assert props == ['disabled']
def test_requires_transitive_bis():
a = BoolOption('activate_service', '', True)
abis = BoolOption('activate_service_bis', '', True)

View File

@ -0,0 +1,14 @@
# Copyright (C) 2012-2013 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
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

View File

@ -1,18 +1,17 @@
# Copyright (C) 2012-2013 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 General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# 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
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# The original `Config` design model is unproudly borrowed from
# the rough gus of pypy: pypy: http://codespeak.net/svn/pypy/dist/pypy/config/
@ -20,13 +19,13 @@
# ____________________________________________________________
"enables us to carry out a calculation and return an option's value"
from tiramisu.error import PropertiesOptionError, ConfigError
from tiramisu.setting import multitypes
from tiramisu.i18n import _
from tiramisu.setting import undefined
# ____________________________________________________________
def carry_out_calculation(option, config, callback, callback_params,
index=None, max_len=None):
index=undefined):
"""a function that carries out a calculation for an option's value
:param option: the option
@ -39,8 +38,6 @@ def carry_out_calculation(option, config, callback, callback_params,
:type callback_params: dict
:param index: if an option is multi, only calculates the nth value
:type index: int
:param max_len: max length for a multi
:type max_len: int
The callback_params is a dict. Key is used to build args (if key is '')
and kwargs (otherwise). Values are tuple of:
@ -130,7 +127,7 @@ def carry_out_calculation(option, config, callback, callback_params,
* if callback_params={'value': ((opt1, False), (opt2, False))}
=> raises ValueError()
If index is not None, return a value, otherwise return:
If index is not undefined, return a value, otherwise return:
* a list if one parameters have multi option
* a value otherwise
@ -146,39 +143,37 @@ def carry_out_calculation(option, config, callback, callback_params,
for key, callbacks in callback_params.items():
for callbk in callbacks:
if isinstance(callbk, tuple):
# callbk is something link (opt, True|False)
opt, force_permissive = callbk
path = config.cfgimpl_get_description().impl_get_path_by_opt(
opt)
# get value
try:
value = config._getattr(path, force_permissive=True, validate=False)
# convert to list, not modifie this multi
if value.__class__.__name__ == 'Multi':
value = list(value)
except PropertiesOptionError as err:
if force_permissive:
continue
raise ConfigError(_('unable to carry out a calculation, '
'option {0} has properties: {1} for: '
'{2}').format(option.impl_getname(),
err.proptype,
option._name))
if len(callbk) == 1:
tcparams.setdefault(key, []).append((config, False))
else:
# callbk is something link (opt, True|False)
opt, force_permissive = callbk
path = config.cfgimpl_get_description(
).impl_get_path_by_opt(opt)
# get value
try:
value = config.getattr(path, force_permissive=True,
validate=False)
# convert to list, not modifie this multi
if value.__class__.__name__ == 'Multi':
value = list(value)
except PropertiesOptionError as err:
if force_permissive:
continue
raise ConfigError(_('unable to carry out a calculation'
', option {0} has properties: {1} '
'for: {2}').format(opt.impl_getname(),
err.proptype,
option.impl_getname()))
is_multi = False
if opt.impl_is_multi():
#opt is master, search if option is a slave
if opt.impl_get_multitype() == multitypes.master:
if option in opt.impl_get_master_slaves():
is_multi = True
#opt is slave, search if option is an other slaves
elif opt.impl_get_multitype() == multitypes.slave:
if option in opt.impl_get_master_slaves().impl_get_master_slaves():
is_multi = True
if is_multi:
len_multi = len(value)
one_is_multi = True
tcparams.setdefault(key, []).append((value, is_multi))
if opt.impl_is_master_slaves() and \
opt.impl_get_master_slaves().in_same_group(option):
len_multi = len(value)
one_is_multi = True
is_multi = True
else:
is_multi = False
tcparams.setdefault(key, []).append((value, is_multi))
else:
# callbk is a value and not a multi
tcparams.setdefault(key, []).append((callbk, False))
@ -188,7 +183,7 @@ def carry_out_calculation(option, config, callback, callback_params,
# if no index, return a list
if one_is_multi:
ret = []
if index:
if index is not undefined:
range_ = [index]
else:
range_ = range(len_multi)
@ -207,7 +202,7 @@ def carry_out_calculation(option, config, callback, callback_params,
else:
kwargs[key] = val
calc = calculate(callback, args, kwargs)
if index:
if index is not undefined:
ret = calc
else:
ret.append(calc)
@ -226,11 +221,7 @@ def carry_out_calculation(option, config, callback, callback_params,
kwargs[key] = couple[0]
ret = calculate(callback, args, kwargs)
if callback_params != {}:
if isinstance(ret, list) and max_len:
ret = ret[:max_len]
if len(ret) < max_len:
ret = ret + [None] * (max_len - len(ret))
if isinstance(ret, list) and index:
if isinstance(ret, list) and index is not undefined:
if len(ret) < index + 1:
ret = None
else:

View File

@ -1,19 +1,18 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2012-2013 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 General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# 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
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# The original `Config` design model is unproudly borrowed from
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
@ -23,7 +22,7 @@
import weakref
from tiramisu.error import PropertiesOptionError, ConfigError
from tiramisu.option import OptionDescription, Option, SymLinkOption
from tiramisu.setting import groups, Settings, default_encoding
from tiramisu.setting import groups, Settings, default_encoding, undefined
from tiramisu.storage import get_storages, get_storage, set_storage, \
_impl_getstate_setting
from tiramisu.value import Values, Multi
@ -63,14 +62,12 @@ class SubConfig(object):
"remove cache (in context)"
self._cfgimpl_get_context().cfgimpl_reset_cache(only_expired, only)
def cfgimpl_get_home_by_path(self, path, force_permissive=False,
force_properties=None):
def cfgimpl_get_home_by_path(self, path, force_permissive=False):
""":returns: tuple (config, name)"""
path = path.split('.')
for step in path[:-1]:
self = self._getattr(step,
force_permissive=force_permissive,
force_properties=force_properties)
self = self.getattr(step,
force_permissive=force_permissive)
return self, path[-1]
#def __hash__(self):
@ -105,18 +102,19 @@ class SubConfig(object):
except PropertiesOptionError:
pass # option with properties
def iter_all(self):
def iter_all(self, force_permissive=False):
"""A way of parsing options **and** groups.
iteration on Options and OptionDescriptions."""
for child in self.cfgimpl_get_description().impl_getchildren():
try:
yield child.impl_getname(), getattr(self, child.impl_getname())
yield child.impl_getname(), self.getattr(child.impl_getname(),
force_permissive=force_permissive)
except GeneratorExit:
raise StopIteration
except PropertiesOptionError:
pass # option with properties
def iter_groups(self, group_type=None):
def iter_groups(self, group_type=None, force_permissive=False):
"""iteration on groups objects only.
All groups are returned if `group_type` is `None`, otherwise the groups
can be filtered by categories (families, or whatever).
@ -134,7 +132,8 @@ class SubConfig(object):
if group_type is None or (group_type is not None and
child.impl_get_group_type()
== group_type):
yield child.impl_getname(), getattr(self, child.impl_getname())
yield child.impl_getname(), self.getattr(child.impl_getname(),
force_permissive=force_permissive)
except GeneratorExit:
raise StopIteration
except PropertiesOptionError:
@ -214,10 +213,14 @@ class SubConfig(object):
self.cfgimpl_get_values().__delitem__(child)
def __getattr__(self, name):
return self._getattr(name)
return self.getattr(name)
def _getattr(self, name, force_permissive=False, force_properties=None,
validate=True):
def _getattr(self, name, force_permissive=False, validate=True):
"""use getattr instead of _getattr
"""
return self.getattr(name, force_permissive, validate)
def getattr(self, name, force_permissive=False, validate=True):
"""
attribute notation mechanism for accessing the value of an option
:param name: attribute name
@ -228,39 +231,36 @@ class SubConfig(object):
# for instance getattr(self, "creole.general.family.adresse_ip_eth0")
if '.' in name:
homeconfig, name = self.cfgimpl_get_home_by_path(
name, force_permissive=force_permissive,
force_properties=force_properties)
return homeconfig._getattr(name, force_permissive=force_permissive,
force_properties=force_properties,
validate=validate)
name, force_permissive=force_permissive)
return homeconfig.getattr(name, force_permissive=force_permissive,
validate=validate)
opt_or_descr = getattr(self.cfgimpl_get_description(), name)
if self._impl_path is None:
subpath = name
else:
subpath = self._impl_path + '.' + name
# symlink options
#FIXME a gerer plutot dans l'option ca ...
#FIXME je n'en sais rien en fait ... :/
if isinstance(opt_or_descr, SymLinkOption):
context = self._cfgimpl_get_context()
path = context.cfgimpl_get_description().impl_get_path_by_opt(
opt_or_descr._opt)
return context._getattr(path, validate=validate,
force_properties=force_properties,
force_permissive=force_permissive)
return context.getattr(path, validate=validate,
force_permissive=force_permissive)
elif isinstance(opt_or_descr, OptionDescription):
self.cfgimpl_get_settings().validate_properties(
opt_or_descr, True, False, path=subpath,
force_permissive=force_permissive,
force_properties=force_properties)
force_permissive=force_permissive)
return SubConfig(opt_or_descr, self._impl_context, subpath)
else:
return self.cfgimpl_get_values().getitem(
return self.cfgimpl_get_values()._get_cached_item(
opt_or_descr, path=subpath,
validate=validate,
force_properties=force_properties,
force_permissive=force_permissive)
def find(self, bytype=None, byname=None, byvalue=None, type_='option',
check_properties=True):
def find(self, bytype=None, byname=None, byvalue=undefined, type_='option',
check_properties=True, force_permissive=False):
"""
finds a list of options recursively in the config
@ -273,10 +273,12 @@ class SubConfig(object):
first=False,
type_=type_,
_subpath=self.cfgimpl_get_path(),
check_properties=check_properties)
check_properties=check_properties,
force_permissive=force_permissive)
def find_first(self, bytype=None, byname=None, byvalue=None,
type_='option', display_error=True, check_properties=True):
def find_first(self, bytype=None, byname=None, byvalue=undefined,
type_='option', display_error=True, check_properties=True,
force_permissive=False):
"""
finds an option recursively in the config
@ -288,10 +290,12 @@ class SubConfig(object):
return self._cfgimpl_get_context()._find(
bytype, byname, byvalue, first=True, type_=type_,
_subpath=self.cfgimpl_get_path(), display_error=display_error,
check_properties=check_properties)
check_properties=check_properties,
force_permissive=force_permissive)
def _find(self, bytype, byname, byvalue, first, type_='option',
_subpath=None, check_properties=True, display_error=True):
_subpath=None, check_properties=True, display_error=True,
force_permissive=False):
"""
convenience method for finding an option that lives only in the subtree
@ -299,10 +303,10 @@ class SubConfig(object):
:return: find list or an exception if nothing has been found
"""
def _filter_by_value():
if byvalue is None:
if byvalue is undefined:
return True
try:
value = getattr(self, path)
value = self.getattr(path, force_permissive=force_permissive)
if isinstance(value, Multi):
return byvalue in value
else:
@ -325,9 +329,10 @@ class SubConfig(object):
if not _filter_by_value():
continue
#remove option with propertyerror, ...
if byvalue is None and check_properties:
if byvalue is undefined and check_properties:
try:
value = getattr(self, path)
value = self.getattr(path,
force_permissive=force_permissive)
except PropertiesOptionError:
# a property restricts the access of the value
continue
@ -355,7 +360,7 @@ class SubConfig(object):
return find_results
def make_dict(self, flatten=False, _currpath=None, withoption=None,
withvalue=None):
withvalue=undefined, force_permissive=False):
"""exports the whole config into a `dict`, for example:
>>> print cfg.make_dict()
@ -395,7 +400,7 @@ class SubConfig(object):
pathsvalues = []
if _currpath is None:
_currpath = []
if withoption is None and withvalue is not None:
if withoption is None and withvalue is not undefined:
raise ValueError(_("make_dict can't filtering with value without "
"option"))
if withoption is not None:
@ -405,14 +410,15 @@ class SubConfig(object):
byvalue=withvalue,
first=False,
type_='path',
_subpath=mypath):
_subpath=mypath,
force_permissive=force_permissive):
path = '.'.join(path.split('.')[:-1])
opt = self._cfgimpl_get_context().cfgimpl_get_description(
).impl_get_opt_by_path(path)
if mypath is not None:
if mypath == path:
withoption = None
withvalue = None
withvalue = undefined
break
else:
tmypath = mypath + '.'
@ -421,32 +427,38 @@ class SubConfig(object):
'should start with {1}'
'').format(path, mypath))
path = path[len(tmypath):]
self._make_sub_dict(opt, path, pathsvalues, _currpath, flatten)
self._make_sub_dict(opt, path, pathsvalues, _currpath, flatten,
force_permissive=force_permissive)
#withoption can be set to None below !
if withoption is None:
for opt in self.cfgimpl_get_description().impl_getchildren():
path = opt.impl_getname()
self._make_sub_dict(opt, path, pathsvalues, _currpath, flatten)
self._make_sub_dict(opt, path, pathsvalues, _currpath, flatten,
force_permissive=force_permissive)
if _currpath == []:
options = dict(pathsvalues)
return options
return pathsvalues
def _make_sub_dict(self, opt, path, pathsvalues, _currpath, flatten):
if isinstance(opt, OptionDescription):
pathsvalues += getattr(self, path).make_dict(flatten,
_currpath +
path.split('.'))
else:
try:
value = self._getattr(opt.impl_getname())
def _make_sub_dict(self, opt, path, pathsvalues, _currpath, flatten,
force_permissive=False):
try:
if isinstance(opt, OptionDescription):
pathsvalues += self.getattr(path,
force_permissive=force_permissive).make_dict(
flatten,
_currpath + path.split('.'),
force_permissive=force_permissive)
else:
value = self.getattr(opt.impl_getname(),
force_permissive=force_permissive)
if flatten:
name = opt.impl_getname()
else:
name = '.'.join(_currpath + [opt.impl_getname()])
pathsvalues.append((name, value))
except PropertiesOptionError:
pass # this just a hidden or disabled option
except PropertiesOptionError:
pass
def cfgimpl_get_path(self):
descr = self.cfgimpl_get_description()
@ -472,14 +484,15 @@ class _CommonConfig(SubConfig):
"read write is a global config's setting, see `settings.py`"
self.cfgimpl_get_settings().read_write()
def getowner(self, opt):
def getowner(self, opt, force_permissive=False):
"""convenience method to retrieve an option's owner
from the config itself
"""
if not isinstance(opt, Option) and not isinstance(opt, SymLinkOption):
raise TypeError(_('opt in getowner must be an option not {0}'
'').format(type(opt)))
return self.cfgimpl_get_values().getowner(opt)
return self.cfgimpl_get_values().getowner(opt,
force_permissive=force_permissive)
def unwrap_from_path(self, path, force_permissive=False):
"""convenience method to extract and Option() object from the Config()
@ -510,7 +523,7 @@ class _CommonConfig(SubConfig):
"""
self._impl_values.set_information(key, value)
def impl_get_information(self, key, default=None):
def impl_get_information(self, key, default=undefined):
"""retrieves one information's item
:param key: the item string (ex: "help")
@ -636,7 +649,7 @@ class GroupConfig(_CommonConfig):
except PropertiesOptionError:
pass
def find_firsts(self, byname=None, bypath=None, byvalue=None,
def find_firsts(self, byname=None, bypath=None, byvalue=undefined,
type_='path', display_error=True):
"""Find first not in current GroupConfig, but in each children
"""
@ -646,7 +659,7 @@ class GroupConfig(_CommonConfig):
try:
if bypath is None and byname is not None and \
isinstance(self, MetaConfig):
bypath = self._find(bytype=None, byvalue=None, byname=byname,
bypath = self._find(bytype=None, byvalue=undefined, byname=byname,
first=True, type_='path',
check_properties=False,
display_error=display_error)
@ -659,7 +672,7 @@ class GroupConfig(_CommonConfig):
if bypath is not None:
#if byvalue is None, try if not raise
value = getattr(child, bypath)
if byvalue is not None:
if byvalue is not undefined:
if isinstance(value, Multi):
if byvalue in value:
ret.append(child)
@ -707,19 +720,5 @@ class MetaConfig(GroupConfig):
def mandatory_warnings(config):
"""convenience function to trace Options that are mandatory and
where no value has been set
:returns: generator of mandatory Option's path
"""
#if value in cache, properties are not calculated
config.cfgimpl_reset_cache(only=('values',))
for path in config.cfgimpl_get_description().impl_getpaths(
include_groups=True):
try:
config._getattr(path, force_properties=frozenset(('mandatory',)))
except PropertiesOptionError as err:
if err.proptype == ['mandatory']:
yield path
config.cfgimpl_reset_cache(only=('values',))
#only for retro-compatibility
return config.cfgimpl_get_values().mandatory_warnings()

View File

@ -1,23 +1,18 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2012-2013 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 General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# 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
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# The original `Config` design model is unproudly borrowed from
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ____________________________________________________________
"user defined exceptions"

View File

@ -1,5 +1,22 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# Copyright (C) 2012-2013 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
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# The original `Config` design model is unproudly borrowed from
# the rough gus of pypy: pypy: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence
"internationalisation utilities"
import gettext
import os

View File

@ -1,1482 +0,0 @@
# -*- coding: utf-8 -*-
"option types and option description"
# Copyright (C) 2012-2013 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 General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# The original `Config` design model is unproudly borrowed from
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence
# ____________________________________________________________
import re
import sys
from types import FunctionType
from IPy import IP
import warnings
from copy import copy
from tiramisu.error import ConfigError, ConflictError, ValueWarning
from tiramisu.setting import groups, multitypes
from tiramisu.i18n import _
from tiramisu.autolib import carry_out_calculation
from tiramisu.storage import get_storages_option
#FIXME : need storage...
#from tiramisu.storage.dictionary.option import StorageBase, StorageOptionDescription
#from tiramisu.storage.sqlalchemy.option import StorageBase, StorageOptionDescription
StorageBase, StorageOptionDescription = get_storages_option()
name_regexp = re.compile(r'^\d+')
forbidden_names = ('iter_all', 'iter_group', 'find', 'find_first',
'make_dict', 'unwrap_from_path', 'read_only',
'read_write', 'getowner', 'set_contexts')
def valid_name(name):
"an option's name is a str and does not start with 'impl' or 'cfgimpl'"
if not isinstance(name, str):
return False
if re.match(name_regexp, name) is None and not name.startswith('_') \
and name not in forbidden_names \
and not name.startswith('impl_') \
and not name.startswith('cfgimpl_'):
return True
else:
return False
#____________________________________________________________
#
class Base(StorageBase):
__slots__ = tuple()
def __init__(self, name, doc, default=None, default_multi=None,
requires=None, multi=False, callback=None,
callback_params=None, validator=None, validator_params=None,
properties=None, warnings_only=False):
if not valid_name(name):
raise ValueError(_("invalid name: {0} for option").format(name))
self._name = name
self._readonly = False
self._informations = {}
self.impl_set_information('doc', doc)
if requires is not None:
self._calc_properties, self._requires = validate_requires_arg(
requires, self._name)
else:
self._calc_properties = frozenset()
self._requires = []
if not multi and default_multi is not None:
raise ValueError(_("a default_multi is set whereas multi is False"
" in option: {0}").format(name))
if default_multi is not None:
try:
self._validate(default_multi)
except ValueError as err:
raise ValueError(_("invalid default_multi value {0} "
"for option {1}: {2}").format(
str(default_multi), name, err))
self._multi = multi
if self._multi:
if default is None:
default = []
self._multitype = multitypes.default
self._default_multi = default_multi
if callback is not None and ((not multi and (default is not None or
default_multi is not None))
or (multi and (default != [] or
default_multi is not None))
):
raise ValueError(_("default value not allowed if option: {0} "
"is calculated").format(name))
if properties is None:
properties = tuple()
if not isinstance(properties, tuple):
raise TypeError(_('invalid properties type {0} for {1},'
' must be a tuple').format(
type(properties),
self._name))
if validator is not None:
validate_callback(validator, validator_params, 'validator')
self._validator = validator
if validator_params is not None:
self._validator_params = validator_params
if callback is None and callback_params is not None:
raise ValueError(_("params defined for a callback function but "
"no callback defined"
" yet for option {0}").format(name))
if callback is not None:
validate_callback(callback, callback_params, 'callback')
self._callback = callback
if callback_params is not None:
self._callback_params = callback_params
if self._calc_properties != frozenset([]) and properties is not tuple():
set_forbidden_properties = self._calc_properties & set(properties)
if set_forbidden_properties != frozenset():
raise ValueError('conflict: properties already set in '
'requirement {0}'.format(
list(set_forbidden_properties)))
if multi and default is None:
self._default = []
else:
self._default = default
self._properties = properties
self._warnings_only = warnings_only
ret = super(Base, self).__init__()
self.impl_validate(self._default)
return ret
class BaseOption(Base):
"""This abstract base class stands for attribute access
in options that have to be set only once, it is of course done in the
__setattr__ method
"""
__slots__ = tuple()
# information
def impl_set_information(self, key, value):
"""updates the information's attribute
(which is a dictionary)
:param key: information's key (ex: "help", "doc"
:param value: information's value (ex: "the help string")
"""
self._informations[key] = value
def impl_get_information(self, key, default=None):
"""retrieves one information's item
:param key: the item string (ex: "help")
"""
if key in self._informations:
return self._informations[key]
elif default is not None:
return default
else:
raise ValueError(_("information's item not found: {0}").format(
key))
# ____________________________________________________________
# serialize object
def _impl_convert_requires(self, descr, load=False):
"""export of the requires during the serialization process
:type descr: :class:`tiramisu.option.OptionDescription`
:param load: `True` if we are at the init of the option description
:type load: bool
"""
if not load and self._requires is None:
self._state_requires = None
elif load and self._state_requires is None:
self._requires = None
del(self._state_requires)
else:
if load:
_requires = self._state_requires
else:
_requires = self._requires
new_value = []
for requires in _requires:
new_requires = []
for require in requires:
if load:
new_require = [descr.impl_get_opt_by_path(require[0])]
else:
new_require = [descr.impl_get_path_by_opt(require[0])]
new_require.extend(require[1:])
new_requires.append(tuple(new_require))
new_value.append(tuple(new_requires))
if load:
del(self._state_requires)
self._requires = new_value
else:
self._state_requires = new_value
# serialize
def _impl_getstate(self, descr):
"""the under the hood stuff that need to be done
before the serialization.
:param descr: the parent :class:`tiramisu.option.OptionDescription`
"""
self._stated = True
for func in dir(self):
if func.startswith('_impl_convert_'):
getattr(self, func)(descr)
self._state_readonly = self._readonly
def __getstate__(self, stated=True):
"""special method to enable the serialization with pickle
Usualy, a `__getstate__` method does'nt need any parameter,
but somme under the hood stuff need to be done before this action
:parameter stated: if stated is `True`, the serialization protocol
can be performed, not ready yet otherwise
:parameter type: bool
"""
try:
self._stated
except AttributeError:
raise SystemError(_('cannot serialize Option, '
'only in OptionDescription'))
slots = set()
for subclass in self.__class__.__mro__:
if subclass is not object:
slots.update(subclass.__slots__)
slots -= frozenset(['_cache_paths', '_cache_consistencies',
'__weakref__'])
states = {}
for slot in slots:
# remove variable if save variable converted
# in _state_xxxx variable
if '_state' + slot not in slots:
if slot.startswith('_state'):
# should exists
states[slot] = getattr(self, slot)
# remove _state_xxx variable
self.__delattr__(slot)
else:
try:
states[slot] = getattr(self, slot)
except AttributeError:
pass
if not stated:
del(states['_stated'])
return states
# unserialize
def _impl_setstate(self, descr):
"""the under the hood stuff that need to be done
before the serialization.
:type descr: :class:`tiramisu.option.OptionDescription`
"""
for func in dir(self):
if func.startswith('_impl_convert_'):
getattr(self, func)(descr, load=True)
try:
self._readonly = self._state_readonly
del(self._state_readonly)
del(self._stated)
except AttributeError:
pass
def __setstate__(self, state):
"""special method that enables us to serialize (pickle)
Usualy, a `__setstate__` method does'nt need any parameter,
but somme under the hood stuff need to be done before this action
:parameter state: a dict is passed to the loads, it is the attributes
of the options object
:type state: dict
"""
for key, value in state.items():
setattr(self, key, value)
def __setattr__(self, name, value):
"""set once and only once some attributes in the option,
like `_name`. `_name` cannot be changed one the option and
pushed in the :class:`tiramisu.option.OptionDescription`.
if the attribute `_readonly` is set to `True`, the option is
"frozen" (which has noting to do with the high level "freeze"
propertie or "read_only" property)
"""
if name not in ('_option', '_is_build_cache') \
and not isinstance(value, tuple):
is_readonly = False
# never change _name
if name == '_name':
try:
if self._name is not None:
#so _name is already set
is_readonly = True
except (KeyError, AttributeError):
pass
elif name != '_readonly':
is_readonly = self.impl_is_readonly()
if is_readonly:
raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
" read-only").format(
self.__class__.__name__,
self._name,
name))
super(BaseOption, self).__setattr__(name, value)
def impl_is_readonly(self):
try:
if self._readonly is True:
return True
except AttributeError:
pass
return False
def impl_getname(self):
return self._name
class OnlyOption(BaseOption):
__slots__ = tuple()
class Option(OnlyOption):
"""
Abstract base class for configuration option's.
Reminder: an Option object is **not** a container for the value.
"""
# __slots__ = ('_multi', '_validator', '_default_multi', '_default',
# '_state_callback', '_callback', '_multitype',
# '_consistencies', '_warnings_only', '_master_slaves',
# '_state_consistencies', '__weakref__')
__slots__ = tuple()
_empty = ''
def __init__(self, name, doc, default=None, default_multi=None,
requires=None, multi=False, callback=None,
callback_params=None, validator=None, validator_params=None,
properties=None, warnings_only=False):
"""
:param name: the option's name
:param doc: the option's description
:param default: specifies the default value of the option,
for a multi : ['bla', 'bla', 'bla']
:param default_multi: 'bla' (used in case of a reset to default only at
a given index)
:param requires: is a list of names of options located anywhere
in the configuration.
:param multi: if true, the option's value is a list
:param callback: the name of a function. If set, the function's output
is responsible of the option's value
:param callback_params: the callback's parameter
:param validator: the name of a function which stands for a custom
validation of the value
:param validator_params: the validator's parameters
:param properties: tuple of default properties
:param warnings_only: _validator and _consistencies don't raise if True
Values()._warning contain message
"""
super(Option, self).__init__(name, doc, default, default_multi,
requires, multi, callback,
callback_params, validator,
validator_params, properties,
warnings_only)
def impl_getrequires(self):
return self._requires
def _launch_consistency(self, func, option, value, context, index,
all_cons_opts):
"""Launch consistency now
:param func: function name, this name should start with _cons_
:type func: `str`
:param option: option that value is changing
:type option: `tiramisu.option.Option`
:param value: new value of this option
:param context: Config's context, if None, check default value instead
:type context: `tiramisu.config.Config`
:param index: only for multi option, consistency should be launch for
specified index
:type index: `int`
:param all_cons_opts: all options concerne by this consistency
:type all_cons_opts: `list` of `tiramisu.option.Option`
"""
if context is not None:
descr = context.cfgimpl_get_description()
all_cons_vals = []
for opt in all_cons_opts:
#get value
if option == opt:
opt_value = value
else:
#if context, calculate value, otherwise get default value
if context is not None:
opt_value = context._getattr(
descr.impl_get_path_by_opt(opt), validate=False,
force_permissive=True)
else:
opt_value = opt.impl_getdefault()
#append value
if not self.impl_is_multi() or option == opt:
all_cons_vals.append(opt_value)
else:
#value is not already set, could be higher index
try:
all_cons_vals.append(opt_value[index])
except IndexError:
#so return if no value
return
getattr(self, func)(all_cons_opts, all_cons_vals)
def impl_validate(self, value, context=None, validate=True,
force_index=None):
"""
:param value: the option's value
:param context: Config's context
:type context: :class:`tiramisu.config.Config`
:param validate: if true enables ``self._validator`` validation
:type validate: boolean
:param force_no_multi: if multi, value has to be a list
not if force_no_multi is True
:type force_no_multi: boolean
"""
if not validate:
return
def val_validator(val):
if self._validator is not None:
if self._validator_params is not None:
validator_params = {}
for val_param, values in self._validator_params.items():
validator_params[val_param] = values
#FIXME ... ca sert à quoi ...
if '' in validator_params:
lst = list(validator_params[''])
lst.insert(0, val)
validator_params[''] = tuple(lst)
else:
validator_params[''] = (val,)
else:
validator_params = {'': (val,)}
# Raise ValueError if not valid
carry_out_calculation(self, config=context,
callback=self._validator,
callback_params=validator_params)
def do_validation(_value, _index=None):
if _value is None:
return
# option validation
try:
self._validate(_value)
except ValueError as err:
raise ValueError(_('invalid value for option {0}: {1}'
'').format(self.impl_getname(), err))
try:
# valid with self._validator
val_validator(_value)
# if not context launch consistency validation
if context is not None:
descr._valid_consistency(self, _value, context, _index)
self._second_level_validation(_value)
except ValueError as err:
msg = _("invalid value for option {0}: {1}").format(
self.impl_getname(), err)
if self._warnings_only:
warnings.warn_explicit(ValueWarning(msg, self),
ValueWarning,
self.__class__.__name__, 0)
else:
raise ValueError(msg)
# generic calculation
if context is not None:
descr = context.cfgimpl_get_description()
if not self._multi or force_index is not None:
do_validation(value, force_index)
else:
if not isinstance(value, list):
raise ValueError(_("invalid value {0} for option {1} which must be a list").format(value, self.impl_getname()))
for index, val in enumerate(value):
do_validation(val, index)
def impl_getdefault(self):
"accessing the default value"
return self._default
def impl_getdefault_multi(self):
"accessing the default value for a multi"
return self._default_multi
def impl_get_multitype(self):
return self._multitype
def impl_get_master_slaves(self):
return self._master_slaves
def impl_is_empty_by_default(self):
"no default value has been set yet"
if ((not self.impl_is_multi() and self._default is None) or
(self.impl_is_multi() and (self._default == []
or None in self._default))):
return True
return False
def impl_getdoc(self):
"accesses the Option's doc"
return self.impl_get_information('doc')
def impl_has_callback(self):
"to know if a callback has been defined or not"
if self._callback is None:
return False
else:
return True
def impl_get_callback(self):
return self._callback, self._callback_params
#def impl_getkey(self, value):
# return value
def impl_is_multi(self):
return self._multi
def impl_add_consistency(self, func, *other_opts):
"""Add consistency means that value will be validate with other_opts
option's values.
:param func: function's name
:type func: `str`
:param other_opts: options used to validate value
:type other_opts: `list` of `tiramisu.option.Option`
"""
if self.impl_is_readonly():
raise AttributeError(_("'{0}' ({1}) cannont add consistency, option is"
" read-only").format(
self.__class__.__name__,
self._name))
for opt in other_opts:
if not isinstance(opt, Option):
raise ConfigError(_('consistency should be set with an option'))
if self is opt:
raise ConfigError(_('cannot add consistency with itself'))
if self.impl_is_multi() != opt.impl_is_multi():
raise ConfigError(_('every options in consistency should be '
'multi or none'))
func = '_cons_{0}'.format(func)
all_cons_opts = tuple([self] + list(other_opts))
value = self.impl_getdefault()
if value is not None:
if self.impl_is_multi():
for idx, val in enumerate(value):
self._launch_consistency(func, self, val, None,
idx, all_cons_opts)
else:
self._launch_consistency(func, self, value, None,
None, all_cons_opts)
self._add_consistency(func, all_cons_opts)
self.impl_validate(self.impl_getdefault())
def _cons_not_equal(self, opts, vals):
for idx_inf, val_inf in enumerate(vals):
for idx_sup, val_sup in enumerate(vals[idx_inf + 1:]):
if val_inf == val_sup is not None:
raise ValueError(_("same value for {0} and {1}").format(
opts[idx_inf].impl_getname(), opts[idx_inf + idx_sup + 1].impl_getname()))
def _impl_convert_callbacks(self, descr, load=False):
if not load and self._callback is None:
self._state_callback = None
elif load and self._state_callback is None:
self._callback = None
del(self._state_callback)
else:
if load:
callback, callback_params = self._state_callback
else:
callback, callback_params = self._callback
if callback_params is not None:
cllbck_prms = {}
for key, values in callback_params.items():
vls = []
for value in values:
if isinstance(value, tuple):
if load:
value = (descr.impl_get_opt_by_path(value[0]),
value[1])
else:
value = (descr.impl_get_path_by_opt(value[0]),
value[1])
vls.append(value)
cllbck_prms[key] = tuple(vls)
else:
cllbck_prms = None
if load:
del(self._state_callback)
self._callback = (callback, cllbck_prms)
else:
self._state_callback = (callback, cllbck_prms)
# serialize/unserialize
def _impl_convert_consistencies(self, descr, load=False):
"""during serialization process, many things have to be done.
one of them is the localisation of the options.
The paths are set once for all.
:type descr: :class:`tiramisu.option.OptionDescription`
:param load: `True` if we are at the init of the option description
:type load: bool
"""
if not load and self._consistencies is None:
self._state_consistencies = None
elif load and self._state_consistencies is None:
self._consistencies = None
del(self._state_consistencies)
else:
if load:
consistencies = self._state_consistencies
else:
consistencies = self._consistencies
new_value = []
for consistency in consistencies:
values = []
for obj in consistency[1]:
if load:
values.append(descr.impl_get_opt_by_path(obj))
else:
values.append(descr.impl_get_path_by_opt(obj))
new_value.append((consistency[0], tuple(values)))
if load:
del(self._state_consistencies)
self._consistencies = new_value
else:
self._state_consistencies = new_value
def _second_level_validation(self, value):
pass
class ChoiceOption(Option):
"""represents a choice out of several objects.
The option can also have the value ``None``
"""
__slots__ = tuple()
def __init__(self, name, doc, values, default=None, default_multi=None,
requires=None, multi=False, callback=None,
callback_params=None, open_values=False, validator=None,
validator_params=None, properties=None, warnings_only=False):
"""
:param values: is a list of values the option can possibly take
"""
if not isinstance(values, tuple):
raise TypeError(_('values must be a tuple for {0}').format(name))
if open_values not in (True, False):
raise TypeError(_('open_values must be a boolean for '
'{0}').format(name))
self._extra = {'_choice_open_values': open_values,
'_choice_values': values}
super(ChoiceOption, self).__init__(name, doc, default=default,
default_multi=default_multi,
callback=callback,
callback_params=callback_params,
requires=requires,
multi=multi,
validator=validator,
validator_params=validator_params,
properties=properties,
warnings_only=warnings_only)
def impl_get_values(self):
return self._extra['_choice_values']
def impl_is_openvalues(self):
return self._extra['_choice_open_values']
def _validate(self, value):
if not self.impl_is_openvalues() and not value in self.impl_get_values():
raise ValueError(_('value {0} is not permitted, '
'only {1} is allowed'
'').format(value, self._extra['_choice_values']))
class BoolOption(Option):
"represents a choice between ``True`` and ``False``"
__slots__ = tuple()
def _validate(self, value):
if not isinstance(value, bool):
raise ValueError(_('invalid boolean'))
class IntOption(Option):
"represents a choice of an integer"
__slots__ = tuple()
def _validate(self, value):
if not isinstance(value, int):
raise ValueError(_('invalid integer'))
class FloatOption(Option):
"represents a choice of a floating point number"
__slots__ = tuple()
def _validate(self, value):
if not isinstance(value, float):
raise ValueError(_('invalid float'))
class StrOption(Option):
"represents the choice of a string"
__slots__ = tuple()
def _validate(self, value):
if not isinstance(value, str):
raise ValueError(_('invalid string'))
if sys.version_info[0] >= 3:
#UnicodeOption is same as StrOption in python 3+
class UnicodeOption(StrOption):
__slots__ = tuple()
pass
else:
class UnicodeOption(Option):
"represents the choice of a unicode string"
__slots__ = tuple()
_empty = u''
def _validate(self, value):
if not isinstance(value, unicode):
raise ValueError(_('invalid unicode'))
class SymLinkOption(OnlyOption):
#FIXME : et avec sqlalchemy ca marche vraiment ?
__slots__ = ('_opt',)
#not return _opt consistencies
#_consistencies = None
def __init__(self, name, opt):
self._name = name
if not isinstance(opt, Option):
raise ValueError(_('malformed symlinkoption '
'must be an option '
'for symlink {0}').format(name))
self._opt = opt
self._readonly = True
return super(Base, self).__init__()
def __getattr__(self, name):
if name in ('_opt', '_opt_type', '_readonly', 'impl_getname'):
return object.__getattr__(self, name)
else:
return getattr(self._opt, name)
def _impl_getstate(self, descr):
super(SymLinkOption, self)._impl_getstate(descr)
self._state_opt = descr.impl_get_path_by_opt(self._opt)
def _impl_setstate(self, descr):
self._opt = descr.impl_get_opt_by_path(self._state_opt)
del(self._state_opt)
super(SymLinkOption, self)._impl_setstate(descr)
def impl_get_information(self, key, default=None):
return self._opt.impl_get_information(key, default)
class IPOption(Option):
"represents the choice of an ip"
__slots__ = tuple()
def __init__(self, name, doc, default=None, default_multi=None,
requires=None, multi=False, callback=None,
callback_params=None, validator=None, validator_params=None,
properties=None, private_only=False, allow_reserved=False,
warnings_only=False):
self._extra = {'_private_only': private_only, '_allow_reserved': allow_reserved}
super(IPOption, self).__init__(name, doc, default=default,
default_multi=default_multi,
callback=callback,
callback_params=callback_params,
requires=requires,
multi=multi,
validator=validator,
validator_params=validator_params,
properties=properties,
warnings_only=warnings_only)
def _validate(self, value):
# sometimes an ip term starts with a zero
# but this does not fit in some case, for example bind does not like it
try:
for val in value.split('.'):
if val.startswith("0") and len(val) > 1:
raise ValueError(_('invalid IP'))
except AttributeError:
#if integer for example
raise ValueError(_('invalid IP'))
# 'standard' validation
try:
IP('{0}/32'.format(value))
except ValueError:
raise ValueError(_('invalid IP'))
def _second_level_validation(self, value):
ip = IP('{0}/32'.format(value))
if not self._extra['_allow_reserved'] and ip.iptype() == 'RESERVED':
raise ValueError(_("invalid IP, mustn't not be in reserved class"))
if self._extra['_private_only'] and not ip.iptype() == 'PRIVATE':
raise ValueError(_("invalid IP, must be in private class"))
class PortOption(Option):
"""represents the choice of a port
The port numbers are divided into three ranges:
the well-known ports,
the registered ports,
and the dynamic or private ports.
You can actived this three range.
Port number 0 is reserved and can't be used.
see: http://en.wikipedia.org/wiki/Port_numbers
"""
__slots__ = tuple()
def __init__(self, name, doc, default=None, default_multi=None,
requires=None, multi=False, callback=None,
callback_params=None, validator=None, validator_params=None,
properties=None, allow_range=False, allow_zero=False,
allow_wellknown=True, allow_registred=True,
allow_private=False, warnings_only=False):
extra = {'_allow_range': allow_range,
'_min_value': None,
'_max_value': None}
ports_min = [0, 1, 1024, 49152]
ports_max = [0, 1023, 49151, 65535]
is_finally = False
for index, allowed in enumerate([allow_zero,
allow_wellknown,
allow_registred,
allow_private]):
if extra['_min_value'] is None:
if allowed:
extra['_min_value'] = ports_min[index]
elif not allowed:
is_finally = True
elif allowed and is_finally:
raise ValueError(_('inconsistency in allowed range'))
if allowed:
extra['_max_value'] = ports_max[index]
if extra['_max_value'] is None:
raise ValueError(_('max value is empty'))
self._extra = extra
super(PortOption, self).__init__(name, doc, default=default,
default_multi=default_multi,
callback=callback,
callback_params=callback_params,
requires=requires,
multi=multi,
validator=validator,
validator_params=validator_params,
properties=properties,
warnings_only=warnings_only)
def _validate(self, value):
if self._extra['_allow_range'] and ":" in str(value):
value = str(value).split(':')
if len(value) != 2:
raise ValueError(_('invalid part, range must have two values '
'only'))
if not value[0] < value[1]:
raise ValueError(_('invalid port, first port in range must be'
' smaller than the second one'))
else:
value = [value]
for val in value:
try:
if not self._extra['_min_value'] <= int(val) <= self._extra['_max_value']:
raise ValueError(_('invalid port, must be an between {0} '
'and {1}').format(
self._extra['_min_value'],
self._extra['_max_value']))
except ValueError:
raise ValueError(_('invalid port'))
class NetworkOption(Option):
"represents the choice of a network"
__slots__ = tuple()
def _validate(self, value):
try:
IP(value)
except ValueError:
raise ValueError(_('invalid network address'))
def _second_level_validation(self, value):
ip = IP(value)
if ip.iptype() == 'RESERVED':
raise ValueError(_("invalid network address, must not be in reserved class"))
class NetmaskOption(Option):
"represents the choice of a netmask"
__slots__ = tuple()
def _validate(self, value):
try:
IP('0.0.0.0/{0}'.format(value))
except ValueError:
raise ValueError(_('invalid netmask address'))
def _cons_network_netmask(self, opts, vals):
#opts must be (netmask, network) options
if None in vals:
return
self.__cons_netmask(opts, vals[0], vals[1], False)
def _cons_ip_netmask(self, opts, vals):
#opts must be (netmask, ip) options
if None in vals:
return
self.__cons_netmask(opts, vals[0], vals[1], True)
def __cons_netmask(self, opts, val_netmask, val_ipnetwork, make_net):
if len(opts) != 2:
raise ConfigError(_('invalid len for opts'))
msg = None
try:
ip = IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
make_net=make_net)
#if cidr == 32, ip same has network
if ip.prefixlen() != 32:
try:
IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
make_net=not make_net)
except ValueError:
pass
else:
if make_net:
msg = _("invalid IP {0} ({1}) with netmask {2},"
" this IP is a network")
except ValueError:
if not make_net:
msg = _('invalid network {0} ({1}) with netmask {2}')
if msg is not None:
raise ValueError(msg.format(val_ipnetwork, opts[1].impl_getname(),
val_netmask))
class BroadcastOption(Option):
__slots__ = tuple()
def _validate(self, value):
try:
IP('{0}/32'.format(value))
except ValueError:
raise ValueError(_('invalid broadcast address'))
def _cons_broadcast(self, opts, vals):
if len(vals) != 3:
raise ConfigError(_('invalid len for vals'))
if None in vals:
return
broadcast, network, netmask = vals
if IP('{0}/{1}'.format(network, netmask)).broadcast() != IP(broadcast):
raise ValueError(_('invalid broadcast {0} ({1}) with network {2} '
'({3}) and netmask {4} ({5})').format(
broadcast, opts[0].impl_getname(), network,
opts[1].impl_getname(), netmask, opts[2].impl_getname()))
class DomainnameOption(Option):
"""represents the choice of a domain name
netbios: for MS domain
hostname: to identify the device
domainname:
fqdn: with tld, not supported yet
"""
__slots__ = tuple()
def __init__(self, name, doc, default=None, default_multi=None,
requires=None, multi=False, callback=None,
callback_params=None, validator=None, validator_params=None,
properties=None, allow_ip=False, type_='domainname',
warnings_only=False, allow_without_dot=False):
if type_ not in ['netbios', 'hostname', 'domainname']:
raise ValueError(_('unknown type_ {0} for hostname').format(type_))
self._extra = {'_dom_type': type_}
if allow_ip not in [True, False]:
raise ValueError(_('allow_ip must be a boolean'))
if allow_without_dot not in [True, False]:
raise ValueError(_('allow_without_dot must be a boolean'))
self._extra['_allow_ip'] = allow_ip
self._extra['_allow_without_dot'] = allow_without_dot
end = ''
extrachar = ''
extrachar_mandatory = ''
if self._extra['_dom_type'] == 'netbios':
length = 14
elif self._extra['_dom_type'] == 'hostname':
length = 62
elif self._extra['_dom_type'] == 'domainname':
length = 62
if allow_without_dot is False:
extrachar_mandatory = '\.'
else:
extrachar = '\.'
end = '+[a-z]*'
self._extra['_domain_re'] = re.compile(r'^(?:[a-z][a-z\d\-{0}]{{,{1}}}{2}){3}$'
''.format(extrachar, length,
extrachar_mandatory,
end))
super(DomainnameOption, self).__init__(name, doc, default=default,
default_multi=default_multi,
callback=callback,
callback_params=callback_params,
requires=requires,
multi=multi,
validator=validator,
validator_params=validator_params,
properties=properties,
warnings_only=warnings_only)
def _validate(self, value):
if self._extra['_allow_ip'] is True:
try:
IP('{0}/32'.format(value))
return
except ValueError:
pass
if self._extra['_dom_type'] == 'domainname' and not self._extra['_allow_without_dot'] and \
'.' not in value:
raise ValueError(_("invalid domainname, must have dot"))
if len(value) > 255:
raise ValueError(_("invalid domainname's length (max 255)"))
if len(value) < 2:
raise ValueError(_("invalid domainname's length (min 2)"))
if not self._extra['_domain_re'].search(value):
raise ValueError(_('invalid domainname'))
class EmailOption(DomainnameOption):
__slots__ = tuple()
username_re = re.compile(r"^[\w!#$%&'*+\-/=?^`{|}~.]+$")
def _validate(self, value):
splitted = value.split('@', 1)
try:
username, domain = splitted
except ValueError:
raise ValueError(_('invalid email address, should contains one @'
))
if not self.username_re.search(username):
raise ValueError(_('invalid username in email address'))
super(EmailOption, self)._validate(domain)
class URLOption(DomainnameOption):
__slots__ = tuple()
proto_re = re.compile(r'(http|https)://')
path_re = re.compile(r"^[a-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$")
def _validate(self, value):
match = self.proto_re.search(value)
if not match:
raise ValueError(_('invalid url, should start with http:// or '
'https://'))
value = value[len(match.group(0)):]
# get domain/files
splitted = value.split('/', 1)
try:
domain, files = splitted
except ValueError:
domain = value
files = None
# if port in domain
splitted = domain.split(':', 1)
try:
domain, port = splitted
except ValueError:
domain = splitted[0]
port = 0
if not 0 <= int(port) <= 65535:
raise ValueError(_('invalid url, port must be an between 0 and '
'65536'))
# validate domainname
super(URLOption, self)._validate(domain)
# validate file
if files is not None and files != '' and not self.path_re.search(files):
raise ValueError(_('invalid url, should ends with filename'))
class FilenameOption(Option):
__slots__ = tuple()
path_re = re.compile(r"^[a-zA-Z0-9\-\._~/+]+$")
def _validate(self, value):
match = self.path_re.search(value)
if not match:
raise ValueError(_('invalid filename'))
class OptionDescription(BaseOption, StorageOptionDescription):
"""Config's schema (organisation, group) and container of Options
The `OptionsDescription` objects lives in the `tiramisu.config.Config`.
"""
__slots__ = tuple()
def __init__(self, name, doc, children, requires=None, properties=None):
"""
:param children: a list of options (including optiondescriptions)
"""
super(OptionDescription, self).__init__(name, doc=doc, requires=requires, properties=properties)
child_names = [child.impl_getname() for child in children]
#better performance like this
valid_child = copy(child_names)
valid_child.sort()
old = None
for child in valid_child:
if child == old:
raise ConflictError(_('duplicate option name: '
'{0}').format(child))
old = child
self._add_children(child_names, children)
self._cache_paths = None
self._cache_consistencies = None
# the group_type is useful for filtering OptionDescriptions in a config
self._group_type = groups.default
self._is_build_cache = False
def impl_getrequires(self):
return self._requires
def impl_getdoc(self):
return self.impl_get_information('doc')
def impl_validate(self, *args):
"""usefull for OptionDescription"""
pass
def impl_getpaths(self, include_groups=False, _currpath=None):
"""returns a list of all paths in self, recursively
_currpath should not be provided (helps with recursion)
"""
if _currpath is None:
_currpath = []
paths = []
for option in self.impl_getchildren():
attr = option.impl_getname()
if isinstance(option, OptionDescription):
if include_groups:
paths.append('.'.join(_currpath + [attr]))
paths += option.impl_getpaths(include_groups=include_groups,
_currpath=_currpath + [attr])
else:
paths.append('.'.join(_currpath + [attr]))
return paths
def impl_build_cache_consistency(self, _consistencies=None, cache_option=None):
#FIXME cache_option !
if _consistencies is None:
init = True
_consistencies = {}
cache_option = []
else:
init = False
for option in self.impl_getchildren():
cache_option.append(option._get_id())
if not isinstance(option, OptionDescription):
for func, all_cons_opts in option._get_consistencies():
for opt in all_cons_opts:
_consistencies.setdefault(opt,
[]).append((func,
all_cons_opts))
else:
option.impl_build_cache_consistency(_consistencies, cache_option)
if init and _consistencies != {}:
self._cache_consistencies = {}
for opt, cons in _consistencies.items():
#FIXME dans le cache ...
if opt._get_id() not in cache_option:
raise ConfigError(_('consistency with option {0} '
'which is not in Config').format(
opt.impl_getname()))
self._cache_consistencies[opt] = tuple(cons)
def impl_validate_options(self, cache_option=None):
"""validate duplicate option and set option has readonly option
"""
if cache_option is None:
init = True
cache_option = []
else:
init = False
for option in self.impl_getchildren():
#FIXME specifique id for sqlalchemy?
#FIXME avec sqlalchemy ca marche le multi parent ? (dans des configs différentes)
#if option.id is None:
# raise SystemError(_("an option's id should not be None "
# "for {0}").format(option.impl_getname()))
if option._get_id() in cache_option:
raise ConflictError(_('duplicate option: {0}').format(option))
cache_option.append(option._get_id())
option._readonly = True
if isinstance(option, OptionDescription):
option.impl_validate_options(cache_option)
if init:
self._readonly = True
# ____________________________________________________________
def impl_set_group_type(self, group_type):
"""sets a given group object to an OptionDescription
:param group_type: an instance of `GroupType` or `MasterGroupType`
that lives in `setting.groups`
"""
if self._group_type != groups.default:
raise TypeError(_('cannot change group_type if already set '
'(old {0}, new {1})').format(self._group_type,
group_type))
if isinstance(group_type, groups.GroupType):
self._group_type = group_type
if isinstance(group_type, groups.MasterGroupType):
#if master (same name has group) is set
#for collect all slaves
slaves = []
master = None
for child in self.impl_getchildren():
if isinstance(child, OptionDescription):
raise ValueError(_("master group {0} shall not have "
"a subgroup").format(self.impl_getname()))
if isinstance(child, SymLinkOption):
raise ValueError(_("master group {0} shall not have "
"a symlinkoption").format(self.impl_getname()))
if not child.impl_is_multi():
raise ValueError(_("not allowed option {0} "
"in group {1}"
": this option is not a multi"
"").format(child.impl_getname(), self.impl_getname()))
if child._name == self.impl_getname():
child._multitype = multitypes.master
master = child
else:
slaves.append(child)
if master is None:
raise ValueError(_('master group with wrong'
' master name for {0}'
).format(self.impl_getname()))
master_callback, master_callback_params = master.impl_get_callback()
if master_callback is not None and master_callback_params is not None:
for key, callbacks in master_callback_params.items():
for callbk in callbacks:
if isinstance(callbk, tuple):
if callbk[0] in slaves:
raise ValueError(_("callback of master's option shall "
"not refered a slave's ones"))
master._master_slaves = tuple(slaves)
for child in self.impl_getchildren():
if child != master:
child._master_slaves = master
child._multitype = multitypes.slave
else:
raise ValueError(_('group_type: {0}'
' not allowed').format(group_type))
def _valid_consistency(self, option, value, context, index):
if self._cache_consistencies is None:
return True
#consistencies is something like [('_cons_not_equal', (opt1, opt2))]
consistencies = self._cache_consistencies.get(option)
if consistencies is not None:
for func, all_cons_opts in consistencies:
#all_cons_opts[0] is the option where func is set
all_cons_opts[0]._launch_consistency(func, option,
value,
context, index,
all_cons_opts)
# ____________________________________________________________
# serialize object
def _impl_getstate(self, descr=None):
"""enables us to export into a dict
:param descr: parent :class:`tiramisu.option.OptionDescription`
"""
if descr is None:
self.impl_build_cache()
descr = self
super(OptionDescription, self)._impl_getstate(descr)
self._state_group_type = str(self._group_type)
for option in self.impl_getchildren():
option._impl_getstate(descr)
def __getstate__(self):
"""special method to enable the serialization with pickle
"""
stated = True
try:
# the `_state` attribute is a flag that which tells us if
# the serialization can be performed
self._stated
except AttributeError:
# if cannot delete, _impl_getstate never launch
# launch it recursivement
# _stated prevent __getstate__ launch more than one time
# _stated is delete, if re-serialize, re-lauch _impl_getstate
self._impl_getstate()
stated = False
return super(OptionDescription, self).__getstate__(stated)
def _impl_setstate(self, descr=None):
"""enables us to import from a dict
:param descr: parent :class:`tiramisu.option.OptionDescription`
"""
if descr is None:
self._cache_paths = None
self._cache_consistencies = None
self.impl_build_cache(force_no_consistencies=True)
descr = self
self._group_type = getattr(groups, self._state_group_type)
del(self._state_group_type)
super(OptionDescription, self)._impl_setstate(descr)
for option in self.impl_getchildren():
option._impl_setstate(descr)
def __setstate__(self, state):
super(OptionDescription, self).__setstate__(state)
try:
self._stated
except AttributeError:
self._impl_setstate()
def validate_requires_arg(requires, name):
"""check malformed requirements
and tranform dict to internal tuple
:param requires: have a look at the
:meth:`tiramisu.setting.Settings.apply_requires` method to
know more about
the description of the requires dictionary
"""
if requires is None:
return None, None
ret_requires = {}
config_action = {}
# start parsing all requires given by user (has dict)
# transforme it to a tuple
for require in requires:
if not type(require) == dict:
raise ValueError(_("malformed requirements type for option:"
" {0}, must be a dict").format(name))
valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
'same_action')
unknown_keys = frozenset(require.keys()) - frozenset(valid_keys)
if unknown_keys != frozenset():
raise ValueError('malformed requirements for option: {0}'
' unknown keys {1}, must only '
'{2}'.format(name,
unknown_keys,
valid_keys))
# prepare all attributes
try:
option = require['option']
expected = require['expected']
action = require['action']
except KeyError:
raise ValueError(_("malformed requirements for option: {0}"
" require must have option, expected and"
" action keys").format(name))
inverse = require.get('inverse', False)
if inverse not in [True, False]:
raise ValueError(_('malformed requirements for option: {0}'
' inverse must be boolean'))
transitive = require.get('transitive', True)
if transitive not in [True, False]:
raise ValueError(_('malformed requirements for option: {0}'
' transitive must be boolean'))
same_action = require.get('same_action', True)
if same_action not in [True, False]:
raise ValueError(_('malformed requirements for option: {0}'
' same_action must be boolean'))
if not isinstance(option, Option):
raise ValueError(_('malformed requirements '
'must be an option in option {0}').format(name))
if option.impl_is_multi():
raise ValueError(_('malformed requirements option {0} '
'should not be a multi').format(name))
if expected is not None:
try:
option._validate(expected)
except ValueError as err:
raise ValueError(_('malformed requirements second argument '
'must be valid for option {0}'
': {1}').format(name, err))
if action in config_action:
if inverse != config_action[action]:
raise ValueError(_("inconsistency in action types"
" for option: {0}"
" action: {1}").format(name, action))
else:
config_action[action] = inverse
if action not in ret_requires:
ret_requires[action] = {}
if option not in ret_requires[action]:
ret_requires[action][option] = (option, [expected], action,
inverse, transitive, same_action)
else:
ret_requires[action][option][1].append(expected)
# transform dict to tuple
ret = []
for opt_requires in ret_requires.values():
ret_action = []
for require in opt_requires.values():
ret_action.append((require[0], tuple(require[1]), require[2],
require[3], require[4], require[5]))
ret.append(tuple(ret_action))
return frozenset(config_action.keys()), tuple(ret)
def validate_callback(callback, callback_params, type_):
if type(callback) != FunctionType:
raise ValueError(_('{0} should be a function').format(type_))
if callback_params is not None:
if not isinstance(callback_params, dict):
raise ValueError(_('{0}_params should be a dict').format(type_))
for key, callbacks in callback_params.items():
if key != '' and len(callbacks) != 1:
raise ValueError(_('{0}_params with key {1} should not have '
'length different to 1').format(type_,
key))
if not isinstance(callbacks, tuple):
raise ValueError(_('{0}_params should be tuple for key "{1}"'
).format(type_, key))
for callbk in callbacks:
if isinstance(callbk, tuple):
option, force_permissive = callbk
if type_ == 'validator' and not force_permissive:
raise ValueError(_('validator not support tuple'))
if not isinstance(option, Option) and not \
isinstance(option, SymLinkOption):
raise ValueError(_('{0}_params should have an option '
'not a {0} for first argument'
).format(type_, type(option)))
if force_permissive not in [True, False]:
raise ValueError(_('{0}_params should have a boolean'
' not a {0} for second argument'
).format(type_, type(
force_permissive)))

View File

@ -0,0 +1,16 @@
from .masterslave import MasterSlaves
from .optiondescription import OptionDescription
from .baseoption import Option, SymLinkOption
from .option import (ChoiceOption, BoolOption, IntOption, FloatOption,
StrOption, UnicodeOption, IPOption, PortOption,
NetworkOption, NetmaskOption, BroadcastOption,
DomainnameOption, EmailOption, URLOption, UsernameOption,
FilenameOption)
__all__ = (MasterSlaves, OptionDescription, Option, SymLinkOption,
ChoiceOption, BoolOption, IntOption, FloatOption,
StrOption, UnicodeOption, IPOption, PortOption,
NetworkOption, NetmaskOption, BroadcastOption,
DomainnameOption, EmailOption, URLOption, UsernameOption,
FilenameOption)

View File

@ -0,0 +1,867 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014 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
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# The original `Config` design model is unproudly borrowed from
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence
# ____________________________________________________________
import re
from copy import copy, deepcopy
from types import FunctionType
import warnings
from tiramisu.i18n import _
from tiramisu.setting import log, undefined
from tiramisu.autolib import carry_out_calculation
from tiramisu.error import ConfigError, ValueWarning
from tiramisu.storage import get_storages_option
StorageBase = get_storages_option('base')
name_regexp = re.compile(r'^\d+')
forbidden_names = ('iter_all', 'iter_group', 'find', 'find_first',
'make_dict', 'unwrap_from_path', 'read_only',
'read_write', 'getowner', 'set_contexts')
def valid_name(name):
"an option's name is a str and does not start with 'impl' or 'cfgimpl'"
if not isinstance(name, str):
return False
if re.match(name_regexp, name) is None and not name.startswith('_') \
and name not in forbidden_names \
and not name.startswith('impl_') \
and not name.startswith('cfgimpl_'):
return True
else:
return False
def validate_callback(callback, callback_params, type_):
if type(callback) != FunctionType:
raise ValueError(_('{0} must be a function').format(type_))
if callback_params is not None:
if not isinstance(callback_params, dict):
raise ValueError(_('{0}_params must be a dict').format(type_))
for key, callbacks in callback_params.items():
if key != '' and len(callbacks) != 1:
raise ValueError(_("{0}_params with key {1} mustn't have "
"length different to 1").format(type_,
key))
if not isinstance(callbacks, tuple):
raise ValueError(_('{0}_params must be tuple for key "{1}"'
).format(type_, key))
for callbk in callbacks:
if isinstance(callbk, tuple):
if len(callbk) == 1:
if callbk != (None,):
raise ValueError(_('{0}_params with length of '
'tuple as 1 must only have '
'None as first value'))
elif len(callbk) != 2:
raise ValueError(_('{0}_params must only have 1 or 2 '
'as length'))
else:
option, force_permissive = callbk
if type_ == 'validator' and not force_permissive:
raise ValueError(_('validator not support tuple'))
if not isinstance(option, Option) and not \
isinstance(option, SymLinkOption):
raise ValueError(_('{0}_params must have an option'
' not a {0} for first argument'
).format(type_, type(option)))
if force_permissive not in [True, False]:
raise ValueError(_('{0}_params must have a boolean'
' not a {0} for second argument'
).format(type_, type(
force_permissive)))
#____________________________________________________________
#
class Base(StorageBase):
__slots__ = tuple()
def __init__(self, name, doc, default=None, default_multi=None,
requires=None, multi=False, callback=None,
callback_params=None, validator=None, validator_params=None,
properties=None, warnings_only=False):
if not valid_name(name):
raise ValueError(_("invalid name: {0} for option").format(name))
self._name = name
self._readonly = False
self._informations = {}
self.impl_set_information('doc', doc)
if requires is not None:
self._calc_properties, self._requires = validate_requires_arg(
requires, self._name)
else:
self._calc_properties = frozenset()
self._requires = []
if not multi and default_multi is not None:
raise ValueError(_("a default_multi is set whereas multi is False"
" in option: {0}").format(name))
if default_multi is not None:
try:
self._validate(default_multi)
except ValueError as err:
raise ValueError(_("invalid default_multi value {0} "
"for option {1}: {2}").format(
str(default_multi), name, err))
self._multi = multi
if self._multi:
if default is None:
default = []
self._default_multi = default_multi
if callback is not None and ((not multi and (default is not None or
default_multi is not None))
or (multi and (default != [] or
default_multi is not None))
):
raise ValueError(_("default value not allowed if option: {0} "
"is calculated").format(name))
if properties is None:
properties = tuple()
if not isinstance(properties, tuple):
raise TypeError(_('invalid properties type {0} for {1},'
' must be a tuple').format(
type(properties),
self._name))
if validator is not None:
validate_callback(validator, validator_params, 'validator')
self._validator = validator
if validator_params is not None:
self._validator_params = validator_params
if callback is None and callback_params is not None:
raise ValueError(_("params defined for a callback function but "
"no callback defined"
" yet for option {0}").format(name))
if callback is not None:
validate_callback(callback, callback_params, 'callback')
self._callback = callback
if callback_params is not None:
self._callback_params = callback_params
if self._calc_properties != frozenset([]) and properties is not tuple():
set_forbidden_properties = self._calc_properties & set(properties)
if set_forbidden_properties != frozenset():
raise ValueError('conflict: properties already set in '
'requirement {0}'.format(
list(set_forbidden_properties)))
if multi and default is None:
self._default = []
else:
self._default = default
self._properties = properties
self._warnings_only = warnings_only
ret = super(Base, self).__init__()
self.impl_validate(self._default)
return ret
class BaseOption(Base):
"""This abstract base class stands for attribute access
in options that have to be set only once, it is of course done in the
__setattr__ method
"""
__slots__ = tuple()
# information
def impl_set_information(self, key, value):
"""updates the information's attribute
(which is a dictionary)
:param key: information's key (ex: "help", "doc"
:param value: information's value (ex: "the help string")
"""
self._informations[key] = value
def impl_get_information(self, key, default=undefined):
"""retrieves one information's item
:param key: the item string (ex: "help")
"""
if key in self._informations:
return self._informations[key]
elif default is not undefined:
return default
else:
raise ValueError(_("information's item not found: {0}").format(
key))
# ____________________________________________________________
# serialize object
def _impl_convert_requires(self, descr, load=False):
"""export of the requires during the serialization process
:type descr: :class:`tiramisu.option.OptionDescription`
:param load: `True` if we are at the init of the option description
:type load: bool
"""
if not load and self._requires is None:
self._state_requires = None
elif load and self._state_requires is None:
self._requires = None
del(self._state_requires)
else:
if load:
_requires = self._state_requires
else:
_requires = self._requires
new_value = []
for requires in _requires:
new_requires = []
for require in requires:
if load:
new_require = [descr.impl_get_opt_by_path(require[0])]
else:
new_require = [descr.impl_get_path_by_opt(require[0])]
new_require.extend(require[1:])
new_requires.append(tuple(new_require))
new_value.append(tuple(new_requires))
if load:
del(self._state_requires)
self._requires = new_value
else:
self._state_requires = new_value
# serialize
def _impl_getstate(self, descr):
"""the under the hood stuff that need to be done
before the serialization.
:param descr: the parent :class:`tiramisu.option.OptionDescription`
"""
self._stated = True
for func in dir(self):
if func.startswith('_impl_convert_'):
getattr(self, func)(descr)
self._state_readonly = self._readonly
def __getstate__(self, stated=True):
"""special method to enable the serialization with pickle
Usualy, a `__getstate__` method does'nt need any parameter,
but somme under the hood stuff need to be done before this action
:parameter stated: if stated is `True`, the serialization protocol
can be performed, not ready yet otherwise
:parameter type: bool
"""
try:
self._stated
except AttributeError:
raise SystemError(_('cannot serialize Option, '
'only in OptionDescription'))
slots = set()
for subclass in self.__class__.__mro__:
if subclass is not object:
slots.update(subclass.__slots__)
slots -= frozenset(['_cache_paths', '_cache_consistencies',
'__weakref__'])
states = {}
for slot in slots:
# remove variable if save variable converted
# in _state_xxxx variable
if '_state' + slot not in slots:
if slot.startswith('_state'):
# should exists
states[slot] = getattr(self, slot)
# remove _state_xxx variable
self.__delattr__(slot)
else:
try:
states[slot] = getattr(self, slot)
except AttributeError:
pass
if not stated:
del(states['_stated'])
return states
# unserialize
def _impl_setstate(self, descr):
"""the under the hood stuff that need to be done
before the serialization.
:type descr: :class:`tiramisu.option.OptionDescription`
"""
for func in dir(self):
if func.startswith('_impl_convert_'):
getattr(self, func)(descr, load=True)
try:
self._readonly = self._state_readonly
del(self._state_readonly)
del(self._stated)
except AttributeError:
pass
def __setstate__(self, state):
"""special method that enables us to serialize (pickle)
Usualy, a `__setstate__` method does'nt need any parameter,
but somme under the hood stuff need to be done before this action
:parameter state: a dict is passed to the loads, it is the attributes
of the options object
:type state: dict
"""
for key, value in state.items():
setattr(self, key, value)
def __setattr__(self, name, value):
"""set once and only once some attributes in the option,
like `_name`. `_name` cannot be changed one the option and
pushed in the :class:`tiramisu.option.OptionDescription`.
if the attribute `_readonly` is set to `True`, the option is
"frozen" (which has noting to do with the high level "freeze"
propertie or "read_only" property)
"""
if name not in ('_option', '_is_build_cache') \
and not isinstance(value, tuple):
is_readonly = False
# never change _name
if name == '_name':
try:
if self._name is not None:
#so _name is already set
is_readonly = True
except (KeyError, AttributeError):
pass
elif name != '_readonly':
is_readonly = self.impl_is_readonly()
if is_readonly:
raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
" read-only").format(
self.__class__.__name__,
self._name,
name))
super(BaseOption, self).__setattr__(name, value)
def impl_is_readonly(self):
try:
if self._readonly is True:
return True
except AttributeError:
pass
return False
def impl_getname(self):
return self._name
class OnlyOption(BaseOption):
__slots__ = tuple()
class Option(OnlyOption):
"""
Abstract base class for configuration option's.
Reminder: an Option object is **not** a container for the value.
"""
# __slots__ = ('_multi', '_validator', '_default_multi', '_default',
# '_state_callback', '_callback',
# '_consistencies', '_warnings_only', '_master_slaves',
# '_state_consistencies', '__weakref__')
__slots__ = tuple()
_empty = ''
def __init__(self, name, doc, default=None, default_multi=None,
requires=None, multi=False, callback=None,
callback_params=None, validator=None, validator_params=None,
properties=None, warnings_only=False):
"""
:param name: the option's name
:param doc: the option's description
:param default: specifies the default value of the option,
for a multi : ['bla', 'bla', 'bla']
:param default_multi: 'bla' (used in case of a reset to default only at
a given index)
:param requires: is a list of names of options located anywhere
in the configuration.
:param multi: if true, the option's value is a list
:param callback: the name of a function. If set, the function's output
is responsible of the option's value
:param callback_params: the callback's parameter
:param validator: the name of a function which stands for a custom
validation of the value
:param validator_params: the validator's parameters
:param properties: tuple of default properties
:param warnings_only: _validator and _consistencies don't raise if True
Values()._warning contain message
"""
super(Option, self).__init__(name, doc, default, default_multi,
requires, multi, callback,
callback_params, validator,
validator_params, properties,
warnings_only)
def impl_getrequires(self):
return self._requires
def _launch_consistency(self, func, option, value, context, index,
all_cons_opts, warnings_only):
"""Launch consistency now
:param func: function name, this name should start with _cons_
:type func: `str`
:param option: option that value is changing
:type option: `tiramisu.option.Option`
:param value: new value of this option
:param context: Config's context, if None, check default value instead
:type context: `tiramisu.config.Config`
:param index: only for multi option, consistency should be launch for
specified index
:type index: `int`
:param all_cons_opts: all options concerne by this consistency
:type all_cons_opts: `list` of `tiramisu.option.Option`
:param warnings_only: specific raise error for warning
:type warnings_only: `boolean`
"""
if context is not None:
descr = context.cfgimpl_get_description()
all_cons_vals = []
for opt in all_cons_opts:
#get value
if option == opt:
opt_value = value
else:
#if context, calculate value, otherwise get default value
if context is not None:
opt_value = context.getattr(
descr.impl_get_path_by_opt(opt), validate=False,
force_permissive=True)
else:
opt_value = opt.impl_getdefault()
#append value
if not self.impl_is_multi() or option == opt:
all_cons_vals.append(opt_value)
else:
#value is not already set, could be higher index
try:
all_cons_vals.append(opt_value[index])
except IndexError:
#so return if no value
return
getattr(self, func)(all_cons_opts, all_cons_vals, warnings_only)
def impl_validate(self, value, context=None, validate=True,
force_index=None):
"""
:param value: the option's value
:param context: Config's context
:type context: :class:`tiramisu.config.Config`
:param validate: if true enables ``self._validator`` validation
:type validate: boolean
:param force_index: if multi, value has to be a list
not if force_index is not None
:type force_index: integer
"""
if not validate:
return
def val_validator(val):
if self._validator is not None:
if self._validator_params is not None:
validator_params = {}
for val_param, values in self._validator_params.items():
validator_params[val_param] = values
#FIXME ... ca sert à quoi ...
if '' in validator_params:
lst = list(validator_params[''])
lst.insert(0, val)
validator_params[''] = tuple(lst)
else:
validator_params[''] = (val,)
else:
validator_params = {'': (val,)}
# Raise ValueError if not valid
carry_out_calculation(self, config=context,
callback=self._validator,
callback_params=validator_params)
def do_validation(_value, _index=None):
if _value is None:
return
# option validation
try:
self._validate(_value)
except ValueError as err:
log.debug('do_validation: value: {0} index: {1}'.format(
_value, _index), exc_info=True)
raise ValueError(_('invalid value for option {0}: {1}'
'').format(self.impl_getname(), err))
error = None
warning = None
try:
# valid with self._validator
val_validator(_value)
# if not context launch consistency validation
if context is not None:
descr._valid_consistency(self, _value, context, _index)
self._second_level_validation(_value, self._warnings_only)
except ValueError as error:
log.debug(_('do_validation for {0}: error in value').format(
self.impl_getname()), exc_info=True)
if self._warnings_only:
warning = error
error = None
except ValueWarning as warning:
log.debug(_('do_validation for {0}: warning in value').format(
self.impl_getname()), exc_info=True)
pass
if error is None and warning is None:
try:
# if context launch consistency validation
if context is not None:
descr._valid_consistency(self, _value, context, _index)
except ValueError as error:
log.debug(_('do_validation for {0}: error in consistency').format(
self.impl_getname()), exc_info=True)
pass
except ValueWarning as warning:
log.debug(_('do_validation for {0}: warning in consistency').format(
self.impl_getname()), exc_info=True)
pass
if warning:
msg = _("warning on the value of the option {0}: {1}").format(
self.impl_getname(), warning)
warnings.warn_explicit(ValueWarning(msg, self),
ValueWarning,
self.__class__.__name__, 0)
elif error:
raise ValueError(_("invalid value for option {0}: {1}").format(
self._name, error))
# generic calculation
if context is not None:
descr = context.cfgimpl_get_description()
if not self._multi or force_index is not None:
do_validation(value, force_index)
else:
if not isinstance(value, list):
raise ValueError(_("invalid value {0} for option {1} which must be a list").format(value, self.impl_getname()))
for index, val in enumerate(value):
do_validation(val, index)
def impl_getdefault(self):
"accessing the default value"
if isinstance(self._default, list):
return copy(self._default)
return self._default
def impl_getdefault_multi(self):
"accessing the default value for a multi"
return self._default_multi
def impl_is_master_slaves(self, type_='both'):
"""FIXME
"""
try:
self._master_slaves
if type_ in ('both', 'master') and \
self._master_slaves.is_master(self):
return True
if type_ in ('both', 'slave') and \
not self._master_slaves.is_master(self):
return True
except:
pass
return False
def impl_get_master_slaves(self):
return self._master_slaves
def impl_is_empty_by_default(self):
"no default value has been set yet"
if ((not self.impl_is_multi() and self._default is None) or
(self.impl_is_multi() and (self._default == []
or None in self._default))):
return True
return False
def impl_getdoc(self):
"accesses the Option's doc"
return self.impl_get_information('doc')
def impl_has_callback(self):
"to know if a callback has been defined or not"
if self._callback is None:
return False
else:
return True
def impl_get_callback(self):
return self._callback, self._callback_params
#def impl_getkey(self, value):
# return value
def impl_is_multi(self):
return self._multi
def impl_add_consistency(self, func, *other_opts, **params):
"""Add consistency means that value will be validate with other_opts
option's values.
:param func: function's name
:type func: `str`
:param other_opts: options used to validate value
:type other_opts: `list` of `tiramisu.option.Option`
:param params: extra params (only warnings_only are allowed)
"""
if self.impl_is_readonly():
raise AttributeError(_("'{0}' ({1}) cannont add consistency, option is"
" read-only").format(
self.__class__.__name__,
self._name))
warnings_only = params.get('warnings_only', False)
for opt in other_opts:
if not isinstance(opt, Option):
raise ConfigError(_('consistency must be set with an option'))
if self is opt:
raise ConfigError(_('cannot add consistency with itself'))
if self.impl_is_multi() != opt.impl_is_multi():
raise ConfigError(_('every options in consistency must be '
'multi or none'))
func = '_cons_{0}'.format(func)
all_cons_opts = tuple([self] + list(other_opts))
value = self.impl_getdefault()
if value is not None:
if self.impl_is_multi():
for idx, val in enumerate(value):
self._launch_consistency(func, self, val, None,
idx, all_cons_opts, warnings_only)
else:
self._launch_consistency(func, self, value, None,
None, all_cons_opts, warnings_only)
self._add_consistency(func, all_cons_opts, params)
self.impl_validate(self.impl_getdefault())
def _cons_not_equal(self, opts, vals, warnings_only):
for idx_inf, val_inf in enumerate(vals):
for idx_sup, val_sup in enumerate(vals[idx_inf + 1:]):
if val_inf == val_sup is not None:
if warnings_only:
msg = _("same value for {0} and {1}, should be different")
else:
msg = _("same value for {0} and {1}, must be different")
raise ValueError(msg.format(opts[idx_inf].impl_getname(),
opts[idx_inf + idx_sup + 1].impl_getname()))
def _impl_convert_callbacks(self, descr, load=False):
if not load and self._callback is None:
self._state_callback = None
elif load and self._state_callback is None:
self._callback = None
del(self._state_callback)
else:
if load:
callback, callback_params = self._state_callback
else:
callback, callback_params = self._callback
if callback_params is not None:
cllbck_prms = {}
for key, values in callback_params.items():
vls = []
for value in values:
if isinstance(value, tuple):
if load:
value = (descr.impl_get_opt_by_path(value[0]),
value[1])
else:
value = (descr.impl_get_path_by_opt(value[0]),
value[1])
vls.append(value)
cllbck_prms[key] = tuple(vls)
else:
cllbck_prms = None
if load:
del(self._state_callback)
self._callback = (callback, cllbck_prms)
else:
self._state_callback = (callback, cllbck_prms)
# serialize/unserialize
def _impl_convert_consistencies(self, descr, load=False):
"""during serialization process, many things have to be done.
one of them is the localisation of the options.
The paths are set once for all.
:type descr: :class:`tiramisu.option.OptionDescription`
:param load: `True` if we are at the init of the option description
:type load: bool
"""
if not load and self._consistencies is None:
self._state_consistencies = None
elif load and self._state_consistencies is None:
self._consistencies = None
del(self._state_consistencies)
else:
if load:
consistencies = self._state_consistencies
else:
consistencies = self._consistencies
new_value = []
for consistency in consistencies:
values = []
for obj in consistency[1]:
if load:
values.append(descr.impl_get_opt_by_path(obj))
else:
values.append(descr.impl_get_path_by_opt(obj))
new_value.append((consistency[0], tuple(values), consistency[2]))
if load:
del(self._state_consistencies)
self._consistencies = new_value
else:
self._state_consistencies = new_value
def _second_level_validation(self, value, warnings_only):
pass
def validate_requires_arg(requires, name):
"""check malformed requirements
and tranform dict to internal tuple
:param requires: have a look at the
:meth:`tiramisu.setting.Settings.apply_requires` method to
know more about
the description of the requires dictionary
"""
if requires is None:
return None, None
ret_requires = {}
config_action = {}
# start parsing all requires given by user (has dict)
# transforme it to a tuple
for require in requires:
if not type(require) == dict:
raise ValueError(_("malformed requirements type for option:"
" {0}, must be a dict").format(name))
valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
'same_action')
unknown_keys = frozenset(require.keys()) - frozenset(valid_keys)
if unknown_keys != frozenset():
raise ValueError('malformed requirements for option: {0}'
' unknown keys {1}, must only '
'{2}'.format(name,
unknown_keys,
valid_keys))
# prepare all attributes
try:
option = require['option']
expected = require['expected']
action = require['action']
except KeyError:
raise ValueError(_("malformed requirements for option: {0}"
" require must have option, expected and"
" action keys").format(name))
if action == 'force_store_value':
raise ValueError(_("malformed requirements for option: {0}"
" action cannot be force_store_value"
).format(name))
inverse = require.get('inverse', False)
if inverse not in [True, False]:
raise ValueError(_('malformed requirements for option: {0}'
' inverse must be boolean'))
transitive = require.get('transitive', True)
if transitive not in [True, False]:
raise ValueError(_('malformed requirements for option: {0}'
' transitive must be boolean'))
same_action = require.get('same_action', True)
if same_action not in [True, False]:
raise ValueError(_('malformed requirements for option: {0}'
' same_action must be boolean'))
if not isinstance(option, Option):
raise ValueError(_('malformed requirements '
'must be an option in option {0}').format(name))
if option.impl_is_multi():
raise ValueError(_('malformed requirements option {0} '
'must not be a multi').format(name))
if expected is not None:
try:
option._validate(expected)
except ValueError as err:
raise ValueError(_('malformed requirements second argument '
'must be valid for option {0}'
': {1}').format(name, err))
if action in config_action:
if inverse != config_action[action]:
raise ValueError(_("inconsistency in action types"
" for option: {0}"
" action: {1}").format(name, action))
else:
config_action[action] = inverse
if action not in ret_requires:
ret_requires[action] = {}
if option not in ret_requires[action]:
ret_requires[action][option] = (option, [expected], action,
inverse, transitive, same_action)
else:
ret_requires[action][option][1].append(expected)
# transform dict to tuple
ret = []
for opt_requires in ret_requires.values():
ret_action = []
for require in opt_requires.values():
ret_action.append((require[0], tuple(require[1]), require[2],
require[3], require[4], require[5]))
ret.append(tuple(ret_action))
return frozenset(config_action.keys()), tuple(ret)
class SymLinkOption(OnlyOption):
#FIXME : et avec sqlalchemy ca marche vraiment ?
__slots__ = ('_opt',)
#not return _opt consistencies
#_consistencies = None
def __init__(self, name, opt):
self._name = name
if not isinstance(opt, Option):
raise ValueError(_('malformed symlinkoption '
'must be an option '
'for symlink {0}').format(name))
self._opt = opt
self._readonly = True
return super(Base, self).__init__()
def __getattr__(self, name):
if name in ('_opt', '_opt_type', '_readonly', 'impl_getname'):
return object.__getattr__(self, name)
else:
return getattr(self._opt, name)
def _impl_getstate(self, descr):
super(SymLinkOption, self)._impl_getstate(descr)
self._state_opt = descr.impl_get_path_by_opt(self._opt)
def _impl_setstate(self, descr):
self._opt = descr.impl_get_opt_by_path(self._state_opt)
del(self._state_opt)
super(SymLinkOption, self)._impl_setstate(descr)
def impl_get_information(self, key, default=undefined):
return self._opt.impl_get_information(key, default)

View File

@ -0,0 +1,188 @@
# -*- coding: utf-8 -*-
"master slave support"
# Copyright (C) 2014 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
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# The original `Config` design model is unproudly borrowed from
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence
# ____________________________________________________________
from tiramisu.i18n import _
from tiramisu.setting import log
from tiramisu.error import SlaveError, ConfigError
from .baseoption import SymLinkOption, Option
class MasterSlaves(object):
__slots__ = ('master', 'slaves')
def __init__(self, name, childs):
#if master (same name has group) is set
#for collect all slaves
self.master = None
slaves = []
for child in childs:
if isinstance(child, SymLinkOption):
raise ValueError(_("master group {0} shall not have "
"a symlinkoption").format(name))
if not isinstance(child, Option):
raise ValueError(_("master group {0} shall not have "
"a subgroup").format(name))
if not child.impl_is_multi():
raise ValueError(_("not allowed option {0} "
"in group {1}"
": this option is not a multi"
"").format(child._name, name))
if child._name == name:
self.master = child
else:
slaves.append(child)
if self.master is None:
raise ValueError(_('master group with wrong'
' master name for {0}'
).format(name))
callback, callback_params = self.master.impl_get_callback()
if callback is not None and callback_params is not None:
for key, callbacks in callback_params.items():
for callbk in callbacks:
if isinstance(callbk, tuple):
if callbk[0] in slaves:
raise ValueError(_("callback of master's option shall "
"not refered a slave's ones"))
#everything is ok, store references
self.slaves = tuple(slaves)
for child in childs:
child._master_slaves = self
def is_master(self, opt):
return opt == self.master
def in_same_group(self, opt):
return opt == self.master or opt in self.slaves
def reset(self, values):
for slave in self.slaves:
values.reset(slave)
def pop(self, values, index):
#FIXME pas test de meta ...
for slave in self.slaves:
if not values.is_default_owner(slave, validate_properties=False,
validate_meta=False):
values._get_cached_item(slave, validate=False,
validate_properties=False
).pop(index, force=True)
pass
def getitem(self, values, opt, path, validate, force_permissive,
force_properties, validate_properties):
if opt == self.master:
value = values._get_validated_value(opt, path, validate,
force_permissive,
force_properties,
validate_properties)
if validate is True:
masterlen = len(value)
for slave in self.slaves:
try:
slave_path = values._get_opt_path(slave)
slave_value = values._get_validated_value(slave,
slave_path,
False,
False,
None, False,
None) # not undefined
slavelen = len(slave_value)
self.validate_slave_length(masterlen, slavelen, slave._name)
except ConfigError:
pass
return value
else:
value = values._get_validated_value(opt, path, validate,
force_permissive,
force_properties,
validate_properties,
None) # not undefined
return self.get_slave_value(values, opt, value, validate, validate_properties)
def setitem(self, values, opt, value, path):
if opt == self.master:
masterlen = len(value)
for slave in self.slaves:
slave_path = values._get_opt_path(slave)
slave_value = values._get_validated_value(slave,
slave_path,
False,
False,
None, False,
None) # not undefined
slavelen = len(slave_value)
self.validate_slave_length(masterlen, slavelen, slave._name)
else:
self.validate_slave_length(self.get_length(values), len(value),
opt._name, setitem=True)
def get_length(self, values, validate=True):
masterp = values._get_opt_path(self.master)
return len(self.getitem(values, self.master, masterp, validate, False,
None, True))
def validate_slave_length(self, masterlen, valuelen, name, setitem=False):
if valuelen > masterlen or (valuelen < masterlen and setitem):
log.debug('validate_slave_length: masterlen: {0}, valuelen: {1}, '
'setitem: {2}'.format(masterlen, valuelen, setitem))
raise SlaveError(_("invalid len for the slave: {0}"
" which has {1} as master").format(
name, self.master._name))
def get_slave_value(self, values, opt, value, validate=True,
validate_properties=True):
"""
if master has length 0:
return []
if master has length bigger than 0:
if default owner:
if has callback:
if return a list:
list same length as master: return list
list is smaller than master: return list + None
list is greater than master: raise SlaveError
if has default value:
list same length as master: return list
list is smaller than master: return list + None
list is greater than master: raise SlaveError
if has default_multi value:
return default_multi * master's length
if has value:
list same length as master: return list
list is smaller than master: return list + None
list is greater than master: raise SlaveError
"""
#if slave, had values until master's one
masterlen = self.get_length(values, validate)
valuelen = len(value)
if validate:
self.validate_slave_length(masterlen, valuelen, opt._name)
path = values._get_opt_path(opt)
if valuelen < masterlen:
for num in range(0, masterlen - valuelen):
index = valuelen + num
value.append(values._get_validated_value(opt, path, True,
False, None,
validate_properties,
index=index),
setitem=False,
force=True)
return value

508
tiramisu/option/option.py Normal file
View File

@ -0,0 +1,508 @@
# -*- coding: utf-8 -*-
"option types and option description"
# Copyright (C) 2012-2013 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
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# The original `Config` design model is unproudly borrowed from
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence
# ____________________________________________________________
import re
import sys
from IPy import IP
from tiramisu.error import ConfigError
from tiramisu.i18n import _
from .baseoption import Option
class ChoiceOption(Option):
"""represents a choice out of several objects.
The option can also have the value ``None``
"""
__slots__ = tuple()
def __init__(self, name, doc, values, default=None, default_multi=None,
requires=None, multi=False, callback=None,
callback_params=None, open_values=False, validator=None,
validator_params=None, properties=None, warnings_only=False):
"""
:param values: is a list of values the option can possibly take
"""
if not isinstance(values, tuple):
raise TypeError(_('values must be a tuple for {0}').format(name))
if open_values not in (True, False):
raise TypeError(_('open_values must be a boolean for '
'{0}').format(name))
self._extra = {'_choice_open_values': open_values,
'_choice_values': values}
super(ChoiceOption, self).__init__(name, doc, default=default,
default_multi=default_multi,
callback=callback,
callback_params=callback_params,
requires=requires,
multi=multi,
validator=validator,
validator_params=validator_params,
properties=properties,
warnings_only=warnings_only)
def impl_get_values(self):
return self._extra['_choice_values']
def impl_is_openvalues(self):
return self._extra['_choice_open_values']
def _validate(self, value):
if not self.impl_is_openvalues() and not value in self.impl_get_values():
raise ValueError(_('value {0} is not permitted, '
'only {1} is allowed'
'').format(value, self._extra['_choice_values']))
class BoolOption(Option):
"represents a choice between ``True`` and ``False``"
__slots__ = tuple()
def _validate(self, value):
if not isinstance(value, bool):
raise ValueError(_('invalid boolean'))
class IntOption(Option):
"represents a choice of an integer"
__slots__ = tuple()
def _validate(self, value):
if not isinstance(value, int):
raise ValueError(_('invalid integer'))
class FloatOption(Option):
"represents a choice of a floating point number"
__slots__ = tuple()
def _validate(self, value):
if not isinstance(value, float):
raise ValueError(_('invalid float'))
class StrOption(Option):
"represents the choice of a string"
__slots__ = tuple()
def _validate(self, value):
if not isinstance(value, str):
raise ValueError(_('invalid string'))
if sys.version_info[0] >= 3:
#UnicodeOption is same as StrOption in python 3+
class UnicodeOption(StrOption):
__slots__ = tuple()
pass
else:
class UnicodeOption(Option):
"represents the choice of a unicode string"
__slots__ = tuple()
_empty = u''
def _validate(self, value):
if not isinstance(value, unicode):
raise ValueError(_('invalid unicode'))
class IPOption(Option):
"represents the choice of an ip"
__slots__ = tuple()
def __init__(self, name, doc, default=None, default_multi=None,
requires=None, multi=False, callback=None,
callback_params=None, validator=None, validator_params=None,
properties=None, private_only=False, allow_reserved=False,
warnings_only=False):
self._extra = {'_private_only': private_only, '_allow_reserved': allow_reserved}
super(IPOption, self).__init__(name, doc, default=default,
default_multi=default_multi,
callback=callback,
callback_params=callback_params,
requires=requires,
multi=multi,
validator=validator,
validator_params=validator_params,
properties=properties,
warnings_only=warnings_only)
def _validate(self, value):
# sometimes an ip term starts with a zero
# but this does not fit in some case, for example bind does not like it
try:
for val in value.split('.'):
if val.startswith("0") and len(val) > 1:
raise ValueError(_('invalid IP'))
except AttributeError:
#if integer for example
raise ValueError(_('invalid IP'))
# 'standard' validation
try:
IP('{0}/32'.format(value))
except ValueError:
raise ValueError(_('invalid IP'))
def _second_level_validation(self, value, warnings_only):
ip = IP('{0}/32'.format(value))
if not self._extra['_allow_reserved'] and ip.iptype() == 'RESERVED':
if warnings_only:
msg = _("IP is in reserved class")
else:
msg = _("invalid IP, mustn't be in reserved class")
raise ValueError(msg)
if self._extra['_private_only'] and not ip.iptype() == 'PRIVATE':
if warnings_only:
msg = _("IP is not in private class")
else:
msg = _("invalid IP, must be in private class")
raise ValueError(msg)
def _cons_in_network(self, opts, vals, warnings_only):
if len(vals) != 3:
raise ConfigError(_('invalid len for vals'))
if None in vals:
return
ip, network, netmask = vals
if IP(ip) not in IP('{0}/{1}'.format(network, netmask)):
if warnings_only:
msg = _('IP {0} ({1}) not in network {2} ({3}) with netmask {4}'
' ({5})')
else:
msg = _('invalid IP {0} ({1}) not in network {2} ({3}) with '
'netmask {4} ({5})')
raise ValueError(msg.format(ip, opts[0].impl_getname(), network,
opts[1].impl_getname(), netmask, opts[2].impl_getname()))
class PortOption(Option):
"""represents the choice of a port
The port numbers are divided into three ranges:
the well-known ports,
the registered ports,
and the dynamic or private ports.
You can actived this three range.
Port number 0 is reserved and can't be used.
see: http://en.wikipedia.org/wiki/Port_numbers
"""
__slots__ = tuple()
def __init__(self, name, doc, default=None, default_multi=None,
requires=None, multi=False, callback=None,
callback_params=None, validator=None, validator_params=None,
properties=None, allow_range=False, allow_zero=False,
allow_wellknown=True, allow_registred=True,
allow_private=False, warnings_only=False):
extra = {'_allow_range': allow_range,
'_min_value': None,
'_max_value': None}
ports_min = [0, 1, 1024, 49152]
ports_max = [0, 1023, 49151, 65535]
is_finally = False
for index, allowed in enumerate([allow_zero,
allow_wellknown,
allow_registred,
allow_private]):
if extra['_min_value'] is None:
if allowed:
extra['_min_value'] = ports_min[index]
elif not allowed:
is_finally = True
elif allowed and is_finally:
raise ValueError(_('inconsistency in allowed range'))
if allowed:
extra['_max_value'] = ports_max[index]
if extra['_max_value'] is None:
raise ValueError(_('max value is empty'))
self._extra = extra
super(PortOption, self).__init__(name, doc, default=default,
default_multi=default_multi,
callback=callback,
callback_params=callback_params,
requires=requires,
multi=multi,
validator=validator,
validator_params=validator_params,
properties=properties,
warnings_only=warnings_only)
def _validate(self, value):
if self._extra['_allow_range'] and ":" in str(value):
value = str(value).split(':')
if len(value) != 2:
raise ValueError(_('invalid port, range must have two values '
'only'))
if not value[0] < value[1]:
raise ValueError(_('invalid port, first port in range must be'
' smaller than the second one'))
else:
value = [value]
for val in value:
try:
val = int(val)
except ValueError:
raise ValueError(_('invalid port'))
if not self._extra['_min_value'] <= val <= self._extra['_max_value']:
raise ValueError(_('invalid port, must be an between {0} '
'and {1}').format(self._extra['_min_value'],
self._extra['_max_value']))
class NetworkOption(Option):
"represents the choice of a network"
__slots__ = tuple()
def _validate(self, value):
try:
IP(value)
except ValueError:
raise ValueError(_('invalid network address'))
def _second_level_validation(self, value, warnings_only):
ip = IP(value)
if ip.iptype() == 'RESERVED':
if warnings_only:
msg = _("network address is in reserved class")
else:
msg = _("invalid network address, mustn't be in reserved class")
raise ValueError(msg)
class NetmaskOption(Option):
"represents the choice of a netmask"
__slots__ = tuple()
def _validate(self, value):
try:
IP('0.0.0.0/{0}'.format(value))
except ValueError:
raise ValueError(_('invalid netmask address'))
def _cons_network_netmask(self, opts, vals, warnings_only):
#opts must be (netmask, network) options
if None in vals:
return
self.__cons_netmask(opts, vals[0], vals[1], False, warnings_only)
def _cons_ip_netmask(self, opts, vals, warnings_only):
#opts must be (netmask, ip) options
if None in vals:
return
self.__cons_netmask(opts, vals[0], vals[1], True, warnings_only)
def __cons_netmask(self, opts, val_netmask, val_ipnetwork, make_net,
warnings_only):
if len(opts) != 2:
raise ConfigError(_('invalid len for opts'))
msg = None
try:
ip = IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
make_net=make_net)
#if cidr == 32, ip same has network
if ip.prefixlen() != 32:
try:
IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
make_net=not make_net)
except ValueError:
pass
else:
if make_net:
msg = _("invalid IP {0} ({1}) with netmask {2},"
" this IP is a network")
except ValueError:
if not make_net:
msg = _('invalid network {0} ({1}) with netmask {2}')
if msg is not None:
raise ValueError(msg.format(val_ipnetwork, opts[1].impl_getname(),
val_netmask))
class BroadcastOption(Option):
__slots__ = tuple()
def _validate(self, value):
try:
IP('{0}/32'.format(value))
except ValueError:
raise ValueError(_('invalid broadcast address'))
def _cons_broadcast(self, opts, vals, warnings_only):
if len(vals) != 3:
raise ConfigError(_('invalid len for vals'))
if None in vals:
return
broadcast, network, netmask = vals
if IP('{0}/{1}'.format(network, netmask)).broadcast() != IP(broadcast):
raise ValueError(_('invalid broadcast {0} ({1}) with network {2} '
'({3}) and netmask {4} ({5})').format(
broadcast, opts[0].impl_getname(), network,
opts[1].impl_getname(), netmask, opts[2].impl_getname()))
class DomainnameOption(Option):
"""represents the choice of a domain name
netbios: for MS domain
hostname: to identify the device
domainname:
fqdn: with tld, not supported yet
"""
__slots__ = tuple()
def __init__(self, name, doc, default=None, default_multi=None,
requires=None, multi=False, callback=None,
callback_params=None, validator=None, validator_params=None,
properties=None, allow_ip=False, type_='domainname',
warnings_only=False, allow_without_dot=False):
if type_ not in ['netbios', 'hostname', 'domainname']:
raise ValueError(_('unknown type_ {0} for hostname').format(type_))
self._extra = {'_dom_type': type_}
if allow_ip not in [True, False]:
raise ValueError(_('allow_ip must be a boolean'))
if allow_without_dot not in [True, False]:
raise ValueError(_('allow_without_dot must be a boolean'))
self._extra['_allow_ip'] = allow_ip
self._extra['_allow_without_dot'] = allow_without_dot
end = ''
extrachar = ''
extrachar_mandatory = ''
if self._extra['_dom_type'] != 'netbios':
allow_number = '\d'
else:
allow_number = ''
if self._extra['_dom_type'] == 'netbios':
length = 14
elif self._extra['_dom_type'] == 'hostname':
length = 62
elif self._extra['_dom_type'] == 'domainname':
length = 62
if allow_without_dot is False:
extrachar_mandatory = '\.'
else:
extrachar = '\.'
end = '+[a-z]*'
self._extra['_domain_re'] = re.compile(r'^(?:[a-z{0}][a-z\d\-{1}]{{,{2}}}{3}){4}$'
''.format(allow_number, extrachar, length,
extrachar_mandatory, end))
super(DomainnameOption, self).__init__(name, doc, default=default,
default_multi=default_multi,
callback=callback,
callback_params=callback_params,
requires=requires,
multi=multi,
validator=validator,
validator_params=validator_params,
properties=properties,
warnings_only=warnings_only)
def _validate(self, value):
if self._extra['_allow_ip'] is True:
try:
IP('{0}/32'.format(value))
return
except ValueError:
pass
if self._extra['_dom_type'] == 'domainname' and not self._extra['_allow_without_dot'] and \
'.' not in value:
raise ValueError(_("invalid domainname, must have dot"))
if len(value) > 255:
raise ValueError(_("invalid domainname's length (max 255)"))
if len(value) < 2:
raise ValueError(_("invalid domainname's length (min 2)"))
if not self._extra['_domain_re'].search(value):
raise ValueError(_('invalid domainname'))
class EmailOption(DomainnameOption):
__slots__ = tuple()
username_re = re.compile(r"^[\w!#$%&'*+\-/=?^`{|}~.]+$")
def _validate(self, value):
splitted = value.split('@', 1)
try:
username, domain = splitted
except ValueError:
raise ValueError(_('invalid email address, must contains one @'
))
if not self.username_re.search(username):
raise ValueError(_('invalid username in email address'))
super(EmailOption, self)._validate(domain)
class URLOption(DomainnameOption):
__slots__ = tuple()
proto_re = re.compile(r'(http|https)://')
path_re = re.compile(r"^[a-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$")
def _validate(self, value):
match = self.proto_re.search(value)
if not match:
raise ValueError(_('invalid url, must start with http:// or '
'https://'))
value = value[len(match.group(0)):]
# get domain/files
splitted = value.split('/', 1)
try:
domain, files = splitted
except ValueError:
domain = value
files = None
# if port in domain
splitted = domain.split(':', 1)
try:
domain, port = splitted
except ValueError:
domain = splitted[0]
port = 0
if not 0 <= int(port) <= 65535:
raise ValueError(_('invalid url, port must be an between 0 and '
'65536'))
# validate domainname
super(URLOption, self)._validate(domain)
# validate file
if files is not None and files != '' and not self.path_re.search(files):
raise ValueError(_('invalid url, must ends with filename'))
class UsernameOption(Option):
__slots__ = tuple()
#regexp build with 'man 8 adduser' informations
username_re = re.compile(r"^[a-z_][a-z0-9_-]{0,30}[$a-z0-9_-]{0,1}$")
def _validate(self, value):
match = self.username_re.search(value)
if not match:
raise ValueError(_('invalid username'))
class FilenameOption(Option):
__slots__ = tuple()
path_re = re.compile(r"^[a-zA-Z0-9\-\._~/+]+$")
def _validate(self, value):
match = self.path_re.search(value)
if not match:
raise ValueError(_('invalid filename'))

View File

@ -0,0 +1,235 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014 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
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# The original `Config` design model is unproudly borrowed from
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence
# ____________________________________________________________
from copy import copy
from tiramisu.i18n import _
from tiramisu.setting import groups, log
from .baseoption import BaseOption
from . import MasterSlaves
from tiramisu.error import ConfigError, ConflictError, ValueWarning
from tiramisu.storage import get_storages_option
StorageOptionDescription = get_storages_option('optiondescription')
class OptionDescription(BaseOption, StorageOptionDescription):
"""Config's schema (organisation, group) and container of Options
The `OptionsDescription` objects lives in the `tiramisu.config.Config`.
"""
__slots__ = tuple()
def __init__(self, name, doc, children, requires=None, properties=None):
"""
:param children: a list of options (including optiondescriptions)
"""
super(OptionDescription, self).__init__(name, doc=doc, requires=requires, properties=properties)
child_names = [child.impl_getname() for child in children]
#better performance like this
valid_child = copy(child_names)
valid_child.sort()
old = None
for child in valid_child:
if child == old:
raise ConflictError(_('duplicate option name: '
'{0}').format(child))
old = child
self._add_children(child_names, children)
self._cache_paths = None
self._cache_consistencies = None
# the group_type is useful for filtering OptionDescriptions in a config
self._group_type = groups.default
self._is_build_cache = False
def impl_getrequires(self):
return self._requires
def impl_getdoc(self):
return self.impl_get_information('doc')
def impl_validate(self, *args):
"""usefull for OptionDescription"""
pass
def impl_getpaths(self, include_groups=False, _currpath=None):
"""returns a list of all paths in self, recursively
_currpath should not be provided (helps with recursion)
"""
if _currpath is None:
_currpath = []
paths = []
for option in self.impl_getchildren():
attr = option.impl_getname()
if isinstance(option, OptionDescription):
if include_groups:
paths.append('.'.join(_currpath + [attr]))
paths += option.impl_getpaths(include_groups=include_groups,
_currpath=_currpath + [attr])
else:
paths.append('.'.join(_currpath + [attr]))
return paths
def impl_build_cache_consistency(self, _consistencies=None, cache_option=None):
#FIXME cache_option !
if _consistencies is None:
init = True
_consistencies = {}
cache_option = []
else:
init = False
for option in self.impl_getchildren():
cache_option.append(option._get_id())
if not isinstance(option, OptionDescription):
for func, all_cons_opts, params in option._get_consistencies():
for opt in all_cons_opts:
_consistencies.setdefault(opt,
[]).append((func,
all_cons_opts,
params))
else:
option.impl_build_cache_consistency(_consistencies, cache_option)
if init and _consistencies != {}:
self._cache_consistencies = {}
for opt, cons in _consistencies.items():
#FIXME dans le cache ...
if opt._get_id() not in cache_option:
raise ConfigError(_('consistency with option {0} '
'which is not in Config').format(
opt.impl_getname()))
self._cache_consistencies[opt] = tuple(cons)
def impl_validate_options(self, cache_option=None):
"""validate duplicate option and set option has readonly option
"""
if cache_option is None:
init = True
cache_option = []
else:
init = False
for option in self.impl_getchildren():
#FIXME specifique id for sqlalchemy?
#FIXME avec sqlalchemy ca marche le multi parent ? (dans des configs différentes)
#if option.id is None:
# raise SystemError(_("an option's id should not be None "
# "for {0}").format(option.impl_getname()))
if option._get_id() in cache_option:
raise ConflictError(_('duplicate option: {0}').format(option))
cache_option.append(option._get_id())
option._readonly = True
if isinstance(option, OptionDescription):
option.impl_validate_options(cache_option)
if init:
self._readonly = True
# ____________________________________________________________
def impl_set_group_type(self, group_type):
"""sets a given group object to an OptionDescription
:param group_type: an instance of `GroupType` or `MasterGroupType`
that lives in `setting.groups`
"""
if self._group_type != groups.default:
raise TypeError(_('cannot change group_type if already set '
'(old {0}, new {1})').format(self._group_type,
group_type))
if isinstance(group_type, groups.GroupType):
self._group_type = group_type
if isinstance(group_type, groups.MasterGroupType):
MasterSlaves(self.impl_getname(), self.impl_getchildren())
else:
raise ValueError(_('group_type: {0}'
' not allowed').format(group_type))
def impl_get_group_type(self):
return self._group_type
def _valid_consistency(self, option, value, context, index):
if self._cache_consistencies is None:
return True
#consistencies is something like [('_cons_not_equal', (opt1, opt2))]
consistencies = self._cache_consistencies.get(option)
if consistencies is not None:
for func, all_cons_opts, params in consistencies:
warnings_only = params.get('warnings_only', False)
#all_cons_opts[0] is the option where func is set
try:
all_cons_opts[0]._launch_consistency(func, option,
value,
context, index,
all_cons_opts,
warnings_only)
except ValueError as err:
if warnings_only:
raise ValueWarning(err.message, option)
else:
raise err
def _impl_getstate(self, descr=None):
"""enables us to export into a dict
:param descr: parent :class:`tiramisu.option.OptionDescription`
"""
if descr is None:
self.impl_build_cache()
descr = self
super(OptionDescription, self)._impl_getstate(descr)
self._state_group_type = str(self._group_type)
for option in self.impl_getchildren():
option._impl_getstate(descr)
def __getstate__(self):
"""special method to enable the serialization with pickle
"""
stated = True
try:
# the `_state` attribute is a flag that which tells us if
# the serialization can be performed
self._stated
except AttributeError:
# if cannot delete, _impl_getstate never launch
# launch it recursivement
# _stated prevent __getstate__ launch more than one time
# _stated is delete, if re-serialize, re-lauch _impl_getstate
self._impl_getstate()
stated = False
return super(OptionDescription, self).__getstate__(stated)
def _impl_setstate(self, descr=None):
"""enables us to import from a dict
:param descr: parent :class:`tiramisu.option.OptionDescription`
"""
if descr is None:
self._cache_paths = None
self._cache_consistencies = None
self.impl_build_cache(force_no_consistencies=True)
descr = self
self._group_type = getattr(groups, self._state_group_type)
del(self._state_group_type)
super(OptionDescription, self)._impl_setstate(descr)
for option in self.impl_getchildren():
option._impl_setstate(descr)
def __setstate__(self, state):
super(OptionDescription, self).__setstate__(state)
try:
self._stated
except AttributeError:
self._impl_setstate()

View File

@ -2,26 +2,22 @@
"sets the options of the configuration objects Config object itself"
# Copyright (C) 2012-2013 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 General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# 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
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# The original `Config` design model is unproudly borrowed from
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ____________________________________________________________
from time import time
from copy import copy
from logging import getLogger
import weakref
from tiramisu.error import (RequirementError, PropertiesOptionError,
ConstError, ConfigError)
@ -104,6 +100,12 @@ rw_append = set(['frozen', 'disabled', 'validator', 'hidden'])
rw_remove = set(['permissive', 'everything_frozen', 'mandatory'])
log = getLogger('tiramisu')
#FIXME
#import logging
#logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
# ____________________________________________________________
class _NameSpace(object):
"""convenient class that emulates a module
@ -212,34 +214,12 @@ def populate_owners():
setattr(owners, 'addowner', addowner)
def populate_multitypes():
"""all multi option should have a type, this type is automaticly set do
not touch this
default
default's multi option set if not master or slave
master
master's option in a group with master's type, name of this option
should be the same name of the optiondescription
slave
slave's option in a group with master's type
"""
setattr(multitypes, 'default', multitypes.DefaultMultiType('default'))
setattr(multitypes, 'master', multitypes.MasterMultiType('master'))
setattr(multitypes, 'slave', multitypes.SlaveMultiType('slave'))
# ____________________________________________________________
# populate groups, owners and multitypes with default attributes
# populate groups and owners with default attributes
groups = GroupModule()
populate_groups()
owners = OwnerModule()
populate_owners()
multitypes = MultiTypeModule()
populate_multitypes()
# ____________________________________________________________
@ -370,7 +350,10 @@ class Settings(object):
self._p_.reset_properties(_path)
self._getcontext().cfgimpl_reset_cache()
def _getproperties(self, opt=None, path=None, is_apply_req=True):
def _getproperties(self, opt=None, path=None, _is_apply_req=True):
"""
be careful, _is_apply_req doesn't copy properties
"""
if opt is None:
props = copy(self._p_.getproperties(path, default_properties))
else:
@ -384,15 +367,16 @@ class Settings(object):
is_cached, props = self._p_.getcache(path, ntime)
if is_cached:
return copy(props)
props = copy(self._p_.getproperties(path, opt._properties))
if is_apply_req:
props = self._p_.getproperties(path, opt._properties)
if _is_apply_req:
props = copy(props)
props |= self.apply_requires(opt, path)
if 'cache' in self:
if 'expire' in self:
if ntime is None:
ntime = int(time())
ntime = ntime + expires_time
self._p_.setcache(path, props, ntime)
if 'cache' in self:
if 'expire' in self:
if ntime is None:
ntime = int(time())
ntime = ntime + expires_time
self._p_.setcache(path, copy(props), ntime)
return props
def append(self, propname):
@ -408,6 +392,10 @@ class Settings(object):
props.remove(propname)
self._setproperties(props, None, None)
def extend(self, propnames):
for propname in propnames:
self.append(propname)
def _setproperties(self, properties, opt, path):
"""save properties for specified opt
(never save properties if same has option properties)
@ -605,7 +593,7 @@ class Settings(object):
" '{0}' with requirement on: "
"'{1}'").format(path, reqpath))
try:
value = context._getattr(reqpath, force_permissive=True)
value = context.getattr(reqpath, force_permissive=True)
except PropertiesOptionError as err:
if not transitive:
continue
@ -642,6 +630,15 @@ class Settings(object):
def get_modified_permissives(self):
return self._p_.get_modified_permissives()
def get_with_property(self, propname):
opts, paths = self._getcontext().cfgimpl_get_description(
)._cache_paths
for index in range(0, len(paths)):
opt = opts[index]
path = paths[index]
if propname in self._getproperties(opt, path, False):
yield (opt, path)
def __getstate__(self):
return {'_p_': self._p_, '_owner': str(self._owner)}

View File

@ -1,22 +1,17 @@
# Copyright (C) 2013 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 General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# 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
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# The original `Config` design model is unproudly borrowed from
# the rough gus of pypy: pypy: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ____________________________________________________________
"""Config's informations are, by default, volatiles. This means, all values and
@ -120,9 +115,12 @@ def get_storages(context, session_id, persistent):
return imp.Settings(storage), imp.Values(storage)
def get_storages_option():
def get_storages_option(type_):
imp = storage_option_type.get()
return imp.Base, imp.OptionDescription
if type_ == 'base':
return imp.Base
else:
return imp.OptionDescription
def list_sessions(type_):

View File

@ -1,20 +1,18 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2013 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 General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# 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
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ____________________________________________________________
"""Default plugin for storage. All informations are store in a simple
dictionary in memory.

View File

@ -2,20 +2,18 @@
"default plugin for setting: set it in a simple dictionary"
# Copyright (C) 2013 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 General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# 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
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ____________________________________________________________
from ..util import Cache

View File

@ -1,20 +1,18 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2013 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 General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# 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
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ____________________________________________________________
from tiramisu.i18n import _
from tiramisu.error import ConfigError

View File

@ -2,20 +2,18 @@
"default plugin for value: set it in a simple dictionary"
# Copyright (C) 2013 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 General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# 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
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ____________________________________________________________
from ..util import Cache

View File

@ -1,20 +1,18 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2013 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 General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# 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
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ____________________________________________________________
"""Sqlite3 plugin for storage. This storage is not made to be used in productive
environment. It was developing as proof of concept.

View File

@ -2,20 +2,18 @@
"default plugin for setting: set it in a simple dictionary"
# Copyright (C) 2013 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 General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# 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
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ____________________________________________________________
from .sqlite3db import Sqlite3DB

View File

@ -2,20 +2,18 @@
"sqlite3 cache"
# Copyright (C) 2013 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 General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# 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
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ____________________________________________________________
try:
from cPickle import loads, dumps

View File

@ -2,20 +2,18 @@
"default plugin for cache: set it in a simple dictionary"
# Copyright (C) 2013 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 General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# 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
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ____________________________________________________________
from os import unlink

View File

@ -2,20 +2,18 @@
"default plugin for value: set it in a simple dictionary"
# Copyright (C) 2013 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 General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# 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
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ____________________________________________________________
from .sqlite3db import Sqlite3DB

View File

@ -2,20 +2,18 @@
"utils used by storage"
# Copyright (C) 2013 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 General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# 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
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ____________________________________________________________
from tiramisu.setting import owners

View File

@ -2,30 +2,27 @@
"takes care of the option's values and multi values"
# Copyright (C) 2013 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 General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# 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
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ____________________________________________________________
from time import time
from copy import copy
import sys
import weakref
from tiramisu.error import ConfigError, SlaveError
from tiramisu.setting import owners, multitypes, expires_time, undefined
from tiramisu.error import ConfigError, SlaveError, PropertiesOptionError
from tiramisu.setting import owners, expires_time, undefined
from tiramisu.autolib import carry_out_calculation
from tiramisu.i18n import _
from tiramisu.option import SymLinkOption
from tiramisu.option import SymLinkOption, OptionDescription
class Values(object):
@ -57,39 +54,73 @@ class Values(object):
raise ConfigError(_('the context does not exist anymore'))
return context
def _getdefault(self, opt):
"""
actually retrieves the default value
:param opt: the `option.Option()` object
"""
meta = self._getcontext().cfgimpl_get_meta()
if meta is not None:
value = meta.cfgimpl_get_values()[opt]
if isinstance(value, Multi):
value = list(value)
else:
value = opt.impl_getdefault()
if opt.impl_is_multi():
return copy(value)
else:
return value
def _getvalue(self, opt, path):
def _getvalue(self, opt, path, is_default, index=undefined):
"""actually retrieves the value
:param opt: the `option.Option()` object
:returns: the option's value (or the default value if not set)
"""
if not self._p_.hasvalue(path):
# if there is no value
value = self._getdefault(opt)
else:
# if there is a value
setting = self._getcontext().cfgimpl_get_settings()
force_default = 'frozen' in setting[opt] and \
'force_default_on_freeze' in setting[opt]
if not is_default and not force_default:
value = self._p_.getvalue(path)
if index is not undefined:
try:
return value[index]
except IndexError:
#value is smaller than expected
#so return default value
pass
else:
return value
#so default value
# if value has callback and is not set
if opt.impl_has_callback():
callback, callback_params = opt.impl_get_callback()
if callback_params is None:
callback_params = {}
value = carry_out_calculation(opt, config=self._getcontext(),
callback=callback,
callback_params=callback_params,
index=index)
try:
if isinstance(value, list) and index is not undefined:
return value[index]
return value
except IndexError:
pass
meta = self._getcontext().cfgimpl_get_meta()
if meta is not None:
#FIXME : problème de longueur si meta + slave
#doit passer de meta à pas meta
#en plus il faut gérer la longueur avec les meta !
#FIXME SymlinkOption
value = meta.cfgimpl_get_values()[opt]
if isinstance(value, Multi):
if index is not undefined:
value = value[index]
else:
value = list(value)
return value
# now try to get default value
value = opt.impl_getdefault()
if opt.impl_is_multi() and index is not undefined:
if value is None:
value = opt.impl_getdefault_multi()
else:
try:
value = value[index]
except IndexError:
value = opt.impl_getdefault_multi()
return value
def get_modified_values(self):
context = self._getcontext()
if context._impl_descr is not None:
for opt, path in context.cfgimpl_get_settings(
).get_with_property('force_store_value'):
self._getowner(opt, path, force_permissive=True)
return self._p_.get_modified_values()
def __contains__(self, opt):
@ -118,44 +149,36 @@ class Values(object):
opt.impl_validate(opt.impl_getdefault(),
context, 'validator' in setting)
context.cfgimpl_reset_cache()
if (opt.impl_is_multi() and
opt.impl_get_multitype() == multitypes.master):
for slave in opt.impl_get_master_slaves():
self.reset(slave)
if opt.impl_is_master_slaves('master'):
opt.impl_get_master_slaves().reset(self)
self._p_.resetvalue(path)
def _isempty(self, opt, value):
"convenience method to know if an option is empty"
empty = opt._empty
if (not opt.impl_is_multi() and (value is None or value == empty)) or \
(opt.impl_is_multi() and (value == [] or
None in value or empty in value)):
return True
return False
def _getcallback_value(self, opt, index=None, max_len=None):
"""
retrieves a value for the options that have a callback
:param opt: the `option.Option()` object
:param index: if an option is multi, only calculates the nth value
:type index: int
:returns: a calculated value
"""
callback, callback_params = opt.impl_get_callback()
if callback_params is None:
callback_params = {}
return carry_out_calculation(opt, config=self._getcontext(),
callback=callback,
callback_params=callback_params,
index=index, max_len=max_len)
if value is not undefined:
empty_not_multi = not opt.impl_is_multi() and (value is None or
value == empty)
empty_multi = opt.impl_is_multi() and (value == [] or
None in value or
empty in value)
else:
empty_multi = empty_not_multi = False
return empty_not_multi or empty_multi
def __getitem__(self, opt):
"enables us to use the pythonic dictionary-like access to values"
return self.getitem(opt)
def getitem(self, opt, path=None, validate=True, force_permissive=False,
force_properties=None, validate_properties=True):
def getitem(self, opt, validate=True, force_permissive=False):
"""
"""
return self._get_cached_item(opt, validate=validate,
force_permissive=force_permissive)
def _get_cached_item(self, opt, path=None, validate=True,
force_permissive=False, force_properties=None,
validate_properties=True):
if path is None:
path = self._get_opt_path(opt)
ntime = None
@ -167,7 +190,7 @@ class Values(object):
if is_cached:
if opt.impl_is_multi() and not isinstance(value, Multi):
#load value so don't need to validate if is not a Multi
value = Multi(value, self.context, opt, path, validate=False)
value = Multi(value, self.context, opt, path)
return value
val = self._getitem(opt, path, validate, force_permissive,
force_properties, validate_properties)
@ -178,85 +201,82 @@ class Values(object):
ntime = int(time())
ntime = ntime + expires_time
self._p_.setcache(path, val, ntime)
return val
def _getitem(self, opt, path, validate, force_permissive, force_properties,
validate_properties):
# options with callbacks
if opt.impl_is_master_slaves():
return opt.impl_get_master_slaves().getitem(self, opt, path,
validate,
force_permissive,
force_properties,
validate_properties)
else:
return self._get_validated_value(opt, path, validate,
force_permissive,
force_properties,
validate_properties)
def _get_validated_value(self, opt, path, validate, force_permissive,
force_properties, validate_properties,
index=undefined):
"""same has getitem but don't touch the cache"""
#FIXME expliquer la différence entre index == undefined et index == None
context = self._getcontext()
setting = context.cfgimpl_get_settings()
is_frozen = 'frozen' in setting[opt]
# For calculating properties, we need value (ie for mandatory value).
# If value is calculating with a PropertiesOptionError's option
# _getcallback_value raise a ConfigError.
# We can not raise ConfigError if this option should raise
# PropertiesOptionError too. So we get config_error and raise
# ConfigError if properties did not raise.
config_error = None
force_permissives = None
# if value has callback and is not set
# or frozen with force_default_on_freeze
if opt.impl_has_callback() and (
self._is_default_owner(path) or
(is_frozen and 'force_default_on_freeze' in setting[opt])):
lenmaster = None
no_value_slave = False
if (opt.impl_is_multi() and
opt.impl_get_multitype() == multitypes.slave):
masterp = self._get_opt_path(opt.impl_get_master_slaves())
mastervalue = context._getattr(masterp, validate=validate)
lenmaster = len(mastervalue)
if lenmaster == 0:
value = []
no_value_slave = True
is_default = self._is_default_owner(opt, path,
validate_properties=False,
validate_meta=False)
try:
if index is None:
gv_index = undefined
else:
gv_index = index
value = self._getvalue(opt, path, is_default, index=gv_index)
config_error = None
except ConfigError as err:
# For calculating properties, we need value (ie for mandatory
# value).
# If value is calculating with a PropertiesOptionError's option
# _getvalue raise a ConfigError.
# We can not raise ConfigError if this option should raise
# PropertiesOptionError too. So we get config_error and raise
# ConfigError if properties did not raise.
# cannot assign config_err directly in python 3.3
config_error = err
# value is not set, for 'undefined' (cannot set None because of
# mandatory property)
value = undefined
if not no_value_slave:
try:
value = self._getcallback_value(opt, max_len=lenmaster)
except ConfigError as err:
# cannot assign config_err directly in python 3.3
config_error = err
value = None
# should not raise PropertiesOptionError if option is
# mandatory
force_permissives = set(['mandatory'])
else:
if (opt.impl_is_multi() and
opt.impl_get_multitype() == multitypes.slave):
if not isinstance(value, list):
value = [value for i in range(lenmaster)]
if config_error is None:
if opt.impl_is_multi():
value = Multi(value, self.context, opt, path, validate)
# suppress value if already set
self.reset(opt, path)
# frozen and force default
elif is_frozen and 'force_default_on_freeze' in setting[opt]:
value = self._getdefault(opt)
if config_error is None:
if index is undefined:
force_index = None
else:
force_index = index
if opt.impl_is_multi():
value = Multi(value, self.context, opt, path, validate)
else:
value = self._getvalue(opt, path)
if opt.impl_is_multi():
# load value so don't need to validate if is not a Multi
value = Multi(value, self.context, opt, path, validate=validate)
if config_error is None and validate:
opt.impl_validate(value, context, 'validator' in setting)
if config_error is None and self._is_default_owner(path) and \
'force_store_value' in setting[opt]:
self.setitem(opt, value, path, is_write=False)
if index is None and not isinstance(value, list):
value = []
if force_index is None:
value = Multi(value, self.context, opt, path)
if validate:
opt.impl_validate(value, context, 'validator' in setting,
force_index=force_index)
#FIXME pas de test avec les metas ...
#FIXME et les symlinkoption ...
if is_default and 'force_store_value' in setting[opt]:
self.setitem(opt, value, path, is_write=False,
force_permissive=force_permissive)
if validate_properties:
setting.validate_properties(opt, False, False, value=value, path=path,
setting.validate_properties(opt, False, False, value=value,
path=path,
force_permissive=force_permissive,
force_properties=force_properties,
force_permissives=force_permissives)
force_properties=force_properties)
if config_error is not None:
raise config_error
return value
def __setitem__(self, opt, value):
raise ValueError('you should only set value with config')
raise ValueError(_('you should only set value with config'))
def setitem(self, opt, value, path, force_permissive=False,
is_write=True):
@ -267,29 +287,13 @@ class Values(object):
opt.impl_validate(value, context,
'validator' in context.cfgimpl_get_settings())
if opt.impl_is_multi():
value = Multi(value, self.context, opt, path, setitem=True)
# Save old value
if opt.impl_get_multitype() == multitypes.master and \
self._p_.hasvalue(path):
old_value = self._p_.getvalue(path)
old_owner = self._p_.getowner(path, None)
else:
old_value = undefined
old_owner = undefined
value = Multi(value, self.context, opt, path)
if opt.impl_is_master_slaves():
opt.impl_get_master_slaves().setitem(self, opt, value, path)
self._setvalue(opt, path, value, force_permissive=force_permissive,
is_write=is_write)
if opt.impl_is_multi() and opt.impl_get_multitype() == multitypes.master:
try:
value._valid_master()
except Exception, err:
if old_value is not undefined:
self._p_.setvalue(path, old_value, old_owner)
else:
self._p_.resetvalue(path)
raise err
def _setvalue(self, opt, path, value, force_permissive=False,
force_properties=None,
is_write=True, validate_properties=True):
context = self._getcontext()
context.cfgimpl_reset_cache()
@ -297,30 +301,35 @@ class Values(object):
setting = context.cfgimpl_get_settings()
setting.validate_properties(opt, False, is_write,
value=value, path=path,
force_permissive=force_permissive,
force_properties=force_properties)
force_permissive=force_permissive)
owner = context.cfgimpl_get_settings().getowner()
if isinstance(value, Multi):
value = list(value)
self._p_.setvalue(path, value, owner)
def getowner(self, opt):
def getowner(self, opt, force_permissive=False):
"""
retrieves the option's owner
:param opt: the `option.Option` object
:param force_permissive: behaves as if the permissive property
was present
:returns: a `setting.owners.Owner` object
"""
if isinstance(opt, SymLinkOption):
opt = opt._opt
path = self._get_opt_path(opt)
return self._getowner(path)
return self._getowner(opt, path, force_permissive=force_permissive)
def _getowner(self, path):
def _getowner(self, opt, path, validate_properties=True,
force_permissive=False, validate_meta=True):
if validate_properties:
self._getitem(opt, path, True, force_permissive, None, True)
owner = self._p_.getowner(path, owners.default)
meta = self._getcontext().cfgimpl_get_meta()
if owner is owners.default and meta is not None:
owner = meta.cfgimpl_get_values()._getowner(path)
if validate_meta:
meta = self._getcontext().cfgimpl_get_meta()
if owner is owners.default and meta is not None:
owner = meta.cfgimpl_get_values()._getowner(opt, path)
return owner
def setowner(self, opt, owner):
@ -334,25 +343,30 @@ class Values(object):
raise TypeError(_("invalid generic owner {0}").format(str(owner)))
path = self._get_opt_path(opt)
self._setowner(path, owner)
self._setowner(opt, path, owner)
def _setowner(self, path, owner):
if self._getowner(path) == owners.default:
def _setowner(self, opt, path, owner):
if self._getowner(opt, path) == owners.default:
raise ConfigError(_('no value for {0} cannot change owner to {1}'
'').format(path, owner))
self._p_.setowner(path, owner)
def is_default_owner(self, opt):
def is_default_owner(self, opt, validate_properties=True,
validate_meta=True):
"""
:param config: *must* be only the **parent** config
(not the toplevel config)
:return: boolean
"""
path = self._get_opt_path(opt)
return self._is_default_owner(path)
return self._is_default_owner(opt, path,
validate_properties=validate_properties,
validate_meta=validate_meta)
def _is_default_owner(self, path):
return self._getowner(path) == owners.default
def _is_default_owner(self, opt, path, validate_properties=True,
validate_meta=True):
return self._getowner(opt, path, validate_properties,
validate_meta=validate_meta) == owners.default
def reset_cache(self, only_expired):
"""
@ -370,7 +384,8 @@ class Values(object):
:param opt: the `option.Option` object
:returns: a string with points like "gc.dummy.my_option"
"""
return self._getcontext().cfgimpl_get_description().impl_get_path_by_opt(opt)
return self._getcontext().cfgimpl_get_description(
).impl_get_path_by_opt(opt)
# information
def set_information(self, key, value):
@ -381,7 +396,7 @@ class Values(object):
"""
self._p_.set_information(key, value)
def get_information(self, key, default=None):
def get_information(self, key, default=undefined):
"""retrieves one information's item
:param key: the item string (ex: "help")
@ -389,12 +404,56 @@ class Values(object):
try:
return self._p_.get_information(key)
except ValueError:
if default is not None:
if default is not undefined:
return default
else:
raise ValueError(_("information's item"
" not found: {0}").format(key))
def mandatory_warnings(self):
"""convenience function to trace Options that are mandatory and
where no value has been set
:returns: generator of mandatory Option's path
"""
def _mandatory_warnings(description):
#if value in cache, properties are not calculated
for opt in description.impl_getchildren():
if isinstance(opt, OptionDescription):
_mandatory_warnings(opt)
elif isinstance(opt, SymLinkOption):
pass
else:
path = self._get_opt_path(opt)
try:
self._get_cached_item(opt, path=path,
force_properties=frozenset(('mandatory',)))
except PropertiesOptionError as err:
if err.proptype == ['mandatory']:
yield path
self.reset_cache(False)
descr = self._getcontext().cfgimpl_get_description()
ret = list(_mandatory_warnings(descr))
self.reset_cache(False)
return ret
def force_cache(self):
"""parse all option to force data in cache
"""
context = self.context()
if not 'cache' in context.cfgimpl_get_settings():
raise ConfigError(_('can force cache only if cache '
'is actived in config'))
#remove all cached properties and value to update "expired" time
context.cfgimpl_reset_cache()
for path in context.cfgimpl_get_description().impl_getpaths(
include_groups=True):
try:
context.getattr(path)
except PropertiesOptionError:
pass
def __getstate__(self):
return {'_p_': self._p_}
@ -414,16 +473,14 @@ class Multi(list):
that support item notation for the values of multi options"""
__slots__ = ('opt', 'path', 'context')
def __init__(self, value, context, opt, path, validate=True,
setitem=False):
def __init__(self, value, context, opt, path):
"""
:param value: the Multi wraps a list value
:param context: the home config that has the values
:param opt: the option object that have this Multi value
:param setitem: only if set a value
"""
if isinstance(value, Multi):
raise ValueError(_('{0} is already a Multi ').format(opt._name))
raise ValueError(_('{0} is already a Multi ').format(opt.impl_getname()))
self.opt = opt
self.path = path
if not isinstance(context, weakref.ReferenceType):
@ -431,11 +488,6 @@ class Multi(list):
self.context = context
if not isinstance(value, list):
value = [value]
if validate and self.opt.impl_get_multitype() == multitypes.slave:
value = self._valid_slave(value, setitem)
elif not setitem and validate and \
self.opt.impl_get_multitype() == multitypes.master:
self._valid_master()
super(Multi, self).__init__(value)
def _getcontext(self):
@ -449,123 +501,73 @@ class Multi(list):
raise ConfigError(_('the context does not exist anymore'))
return context
def _valid_slave(self, value, setitem):
#if slave, had values until master's one
context = self._getcontext()
values = context.cfgimpl_get_values()
masterp = context.cfgimpl_get_description().impl_get_path_by_opt(
self.opt.impl_get_master_slaves())
mastervalue = context._getattr(masterp, validate=False)
masterlen = len(mastervalue)
valuelen = len(value)
if valuelen > masterlen or (valuelen < masterlen and setitem):
raise SlaveError(_("invalid len for the slave: {0}"
" which has {1} as master").format(
self.opt.impl_getname(), masterp))
elif valuelen < masterlen:
for num in range(0, masterlen - valuelen):
if self.opt.impl_has_callback():
# if callback add a value, but this value will not change
# anymore automaticly (because this value has owner)
index = value.__len__()
value.append(values._getcallback_value(self.opt,
index=index))
else:
value.append(self.opt.impl_getdefault_multi())
#else: same len so do nothing
return value
def _valid_master(self):
#masterlen = len(value)
values = self._getcontext().cfgimpl_get_values()
for slave in self.opt._master_slaves:
path = values._get_opt_path(slave)
Multi(values._getvalue(slave, path), self.context, slave, path)
def __setitem__(self, index, value):
self._validate(value, index)
#assume not checking mandatory property
super(Multi, self).__setitem__(index, value)
self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path,
self)
def append(self, value=undefined, force=False):
def append(self, value=undefined, force=False, setitem=True):
"""the list value can be updated (appened)
only if the option is a master
"""
context = self._getcontext()
values = self._getcontext().cfgimpl_get_values()
if not force:
if self.opt.impl_get_multitype() == multitypes.slave:
if self.opt.impl_is_master_slaves('slave'):
raise SlaveError(_("cannot append a value on a multi option {0}"
" which is a slave").format(self.opt.impl_getname()))
elif self.opt.impl_get_multitype() == multitypes.master:
values = context.cfgimpl_get_values()
if value is undefined and self.opt.impl_has_callback():
value = values._getcallback_value(self.opt)
#Force None il return a list
if isinstance(value, list):
value = None
index = self.__len__()
if value is undefined:
value = self.opt.impl_getdefault_multi()
try:
value = values._get_validated_value(self.opt, self.path,
True, False, None, True,
index=index)
except IndexError:
value = None
self._validate(value, index)
super(Multi, self).append(value)
context.cfgimpl_get_values()._setvalue(self.opt, self.path,
self,
validate_properties=not force)
if not force and self.opt.impl_get_multitype() == multitypes.master:
for slave in self.opt.impl_get_master_slaves():
path = values._get_opt_path(slave)
if not values._is_default_owner(path):
if slave.impl_has_callback():
dvalue = values._getcallback_value(slave, index=index)
else:
dvalue = slave.impl_getdefault_multi()
old_value = values.getitem(slave, path, validate=False,
validate_properties=False)
if len(old_value) + 1 != self.__len__():
raise SlaveError(_("invalid len for the slave: {0}"
" which has {1} as master").format(
self.opt._name, self.__len__()))
values.getitem(slave, path, validate=False,
validate_properties=False).append(
dvalue, force=True)
if setitem:
values._setvalue(self.opt, self.path, self,
validate_properties=not force)
def sort(self, cmp=None, key=None, reverse=False):
if self.opt.impl_get_multitype() in [multitypes.slave,
multitypes.master]:
if self.opt.impl_is_master_slaves():
raise SlaveError(_("cannot sort multi option {0} if master or slave"
"").format(self.opt.impl_getname()))
if sys.version_info[0] >= 3:
if cmp is not None:
raise ValueError(_('cmp is not permitted in python v3 or greater'))
raise ValueError(_('cmp is not permitted in python v3 or '
'greater'))
super(Multi, self).sort(key=key, reverse=reverse)
else:
super(Multi, self).sort(cmp=cmp, key=key, reverse=reverse)
self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path,
self)
def reverse(self):
if self.opt.impl_get_multitype() in [multitypes.slave,
multitypes.master]:
if self.opt.impl_is_master_slaves():
raise SlaveError(_("cannot reverse multi option {0} if master or "
"slave").format(self.opt.impl_getname()))
super(Multi, self).reverse()
self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path,
self)
def insert(self, index, obj):
if self.opt.impl_get_multitype() in [multitypes.slave,
multitypes.master]:
if self.opt.impl_is_master_slaves():
raise SlaveError(_("cannot insert multi option {0} if master or "
"slave").format(self.opt.impl_getname()))
super(Multi, self).insert(index, obj)
self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path,
self)
def extend(self, iterable):
if self.opt.impl_get_multitype() in [multitypes.slave,
multitypes.master]:
if self.opt.impl_is_master_slaves():
raise SlaveError(_("cannot extend multi option {0} if master or "
"slave").format(self.opt.impl_getname()))
super(Multi, self).extend(iterable)
self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path,
self)
def _validate(self, value, force_index):
if value is not None:
@ -590,18 +592,14 @@ class Multi(list):
"""
context = self._getcontext()
if not force:
if self.opt.impl_get_multitype() == multitypes.slave:
if self.opt.impl_is_master_slaves('slave'):
raise SlaveError(_("cannot pop a value on a multi option {0}"
" which is a slave").format(self.opt.impl_getname()))
elif self.opt.impl_get_multitype() == multitypes.master:
for slave in self.opt.impl_get_master_slaves():
values = context.cfgimpl_get_values()
if not values.is_default_owner(slave):
#get multi without valid properties
values.getitem(slave, validate=False,
validate_properties=False
).pop(index, force=True)
if self.opt.impl_is_master_slaves('master'):
self.opt.impl_get_master_slaves().pop(
context.cfgimpl_get_values(), index)
#set value without valid properties
ret = super(Multi, self).pop(index)
context.cfgimpl_get_values()._setvalue(self.opt, self.path, self, validate_properties=not force)
context.cfgimpl_get_values()._setvalue(self.opt, self.path, self,
validate_properties=not force)
return ret

View File

@ -2,104 +2,127 @@ msgid ""
msgstr ""
"Project-Id-Version: Tiramisu\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-01-25 11:30+CET\n"
"POT-Creation-Date: 2014-03-29 19:01+CET\n"
"PO-Revision-Date: \n"
"Last-Translator: Emmanuel Garette <egarette@cadoles.com>\n"
"Language-Team: Tiramisu's team <egarette@cadoles.com>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 1.5.4\n"
"Language: fr\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Poedit-SourceCharset: UTF-8\n"
#: tiramisu/autolib.py:159
#: tiramisu/autolib.py:165
msgid ""
"unable to carry out a calculation, option {0} has properties: {1} for: {2}"
msgstr ""
"impossible d'effectuer le calcul, l'option {0} a les propriétés : {1} pour : "
"{2}"
#: tiramisu/config.py:52
#: tiramisu/config.py:51
msgid "descr must be an optiondescription, not {0}"
msgstr "descr doit être une optiondescription pas un {0}"
#: tiramisu/config.py:127
#: tiramisu/config.py:126
msgid "unknown group_type: {0}"
msgstr "group_type inconnu: {0}"
#: tiramisu/config.py:166 tiramisu/setting.py:339 tiramisu/value.py:57
#: tiramisu/value.py:427
#: tiramisu/config.py:163 tiramisu/setting.py:334 tiramisu/value.py:55
#: tiramisu/value.py:500
msgid "the context does not exist anymore"
msgstr "le context n'existe plus"
#: tiramisu/config.py:171
msgid ""
"no option description found for this config (may be metaconfig without meta)"
#: tiramisu/config.py:168
msgid "no option description found for this config (may be GroupConfig)"
msgstr ""
"pas d'option description trouvé pour cette config (peut être une metaconfig "
"sans meta)"
"pas d'option description trouvé pour cette config (peut être un GroupConfig)"
#: tiramisu/config.py:197
#: tiramisu/config.py:194
msgid "can't assign to an OptionDescription"
msgstr "ne peut pas attribuer une valeur à une OptionDescription"
#: tiramisu/config.py:330
#: tiramisu/config.py:324
msgid "unknown type_ type {0}for _find"
msgstr "type_ type {0} pour _find inconnu"
#: tiramisu/config.py:369
#: tiramisu/config.py:363
msgid "no option found in config with these criteria"
msgstr "aucune option trouvée dans la config avec ces critères"
#: tiramisu/config.py:419
#: tiramisu/config.py:413
msgid "make_dict can't filtering with value without option"
msgstr "make_dict ne peut filtrer sur une valeur mais sans option"
#: tiramisu/config.py:440
#: tiramisu/config.py:434
msgid "unexpected path {0}, should start with {1}"
msgstr "chemin imprévu {0}, devrait commencer par {1}"
#: tiramisu/config.py:500
#: tiramisu/config.py:491
msgid "opt in getowner must be an option not {0}"
msgstr "opt dans getowner doit être une option pas {0}"
#: tiramisu/option.py:67
#: tiramisu/config.py:535
msgid "cannot serialize Config with MetaConfig"
msgstr "impossible de sérialiser une Config avec une MetaConfig"
#: tiramisu/config.py:549
msgid "this storage is not serialisable, could be a none persistent storage"
msgstr "ce storage n'est sérialisable, devrait être une storage non persistant"
#: tiramisu/config.py:612
msgid "metaconfig's children must be a list"
msgstr "enfants d'une metaconfig doit être une liste"
#: tiramisu/config.py:706
msgid "metaconfig's children should be config, not {0}"
msgstr "enfants d'une metaconfig doit être une config, pas {0}"
#: tiramisu/config.py:710
msgid "child has already a metaconfig's"
msgstr "enfant a déjà une metaconfig"
#: tiramisu/config.py:714
msgid "all config in metaconfig must have the same optiondescription"
msgstr ""
"toutes les configs d'une metaconfig doivent avoir la même optiondescription"
#: tiramisu/option.py:66
msgid "invalid name: {0} for option"
msgstr "nom invalide : {0} pour l'option"
#: tiramisu/option.py:76
#: tiramisu/option.py:75
msgid "invalid properties type {0} for {1}, must be a tuple"
msgstr "type des properties invalide {0} pour {1}, doit être un tuple"
#: tiramisu/option.py:114
#: tiramisu/option.py:113
msgid "'{0}' ({1}) object attribute '{2}' is read-only"
msgstr "l'attribut {2} de l'objet '{0}' ({1}) est en lecture seule"
#: tiramisu/option.py:141 tiramisu/value.py:376
#: tiramisu/option.py:140 tiramisu/value.py:410
msgid "information's item not found: {0}"
msgstr "aucune config spécifiée alors que c'est nécessaire"
#: tiramisu/option.py:203
#: tiramisu/option.py:202
msgid "cannot serialize Option, only in OptionDescription"
msgstr "ne peut serialiser une Option, seulement via une OptionDescription"
#: tiramisu/option.py:306
#: tiramisu/option.py:305
msgid "a default_multi is set whereas multi is False in option: {0}"
msgstr ""
"une default_multi est renseignée alors que multi est False dans l'option : "
"{0}"
#: tiramisu/option.py:312
#: tiramisu/option.py:311
msgid "invalid default_multi value {0} for option {1}: {2}"
msgstr "la valeur default_multi est invalide {0} pour l'option {1} : {2}"
#: tiramisu/option.py:317
#: tiramisu/option.py:316
msgid "default value not allowed if option: {0} is calculated"
msgstr "la valeur par défaut n'est pas possible si l'option {0} est calculée"
#: tiramisu/option.py:320
#: tiramisu/option.py:319
msgid ""
"params defined for a callback function but no callback defined yet for "
"option {0}"
@ -107,251 +130,289 @@ msgstr ""
"params définis pour une fonction callback mais par de callback encore "
"définis pour l'option {0}"
#: tiramisu/option.py:359
msgid "option not in all_cons_opts"
msgstr "option non présentante dans all_cons_opts"
#: tiramisu/option.py:425 tiramisu/option.py:435
#: tiramisu/option.py:424 tiramisu/option.py:454
msgid "invalid value for option {0}: {1}"
msgstr "valeur invalide pour l'option {0} : {1}"
#: tiramisu/option.py:452
#: tiramisu/option.py:448
msgid "warning on the value of the option {0}: {1}"
msgstr "avertissement sur la valeur de l'option {0} : {1}"
#: tiramisu/option.py:465
msgid "invalid value {0} for option {1} which must be a list"
msgstr "valeur invalide pour l'option {0} : {1} laquelle doit être une liste"
#: tiramisu/option.py:508
msgid "consistency should be set with an option"
#: tiramisu/option.py:523
msgid "consistency must be set with an option"
msgstr "consistency doit être configuré avec une option"
#: tiramisu/option.py:510
#: tiramisu/option.py:525
msgid "cannot add consistency with itself"
msgstr "ne peut ajouter une consistency avec lui même"
#: tiramisu/option.py:512
msgid "every options in consistency should be multi or none"
#: tiramisu/option.py:527
msgid "every options in consistency must be multi or none"
msgstr ""
"toutes les options d'une consistency devrait être multi ou ne pas l'être"
"toutes les options d'une consistency doivent être multi ou ne pas l'être"
#: tiramisu/option.py:532
msgid "same value for {0} and {1}"
msgstr "même valeur pour {0} et {1}"
#: tiramisu/option.py:548
msgid "same value for {0} and {1}, should be different"
msgstr "même valeur pour {0} et {1}, devrait être différent"
#: tiramisu/option.py:641
#: tiramisu/option.py:550
msgid "same value for {0} and {1}, must be different"
msgstr "même valeur pour {0} et {1}, doit être différent"
#: tiramisu/option.py:644
msgid "values must be a tuple for {0}"
msgstr "values doit être un tuple pour {0}"
#: tiramisu/option.py:644
#: tiramisu/option.py:647
msgid "open_values must be a boolean for {0}"
msgstr "open_values doit être un booléen pour {0}"
#: tiramisu/option.py:666
#: tiramisu/option.py:669
msgid "value {0} is not permitted, only {1} is allowed"
msgstr "valeur {0} n'est pas permis, seules {1} sont autorisées"
#: tiramisu/option.py:678
#: tiramisu/option.py:681
msgid "invalid boolean"
msgstr "booléen invalide"
#: tiramisu/option.py:688
#: tiramisu/option.py:691
msgid "invalid integer"
msgstr "nombre invalide"
#: tiramisu/option.py:698
#: tiramisu/option.py:701
msgid "invalid float"
msgstr "invalide nombre flottan"
#: tiramisu/option.py:708
#: tiramisu/option.py:711
msgid "invalid string"
msgstr "invalide caractère"
#: tiramisu/option.py:725
#: tiramisu/option.py:728
msgid "invalid unicode"
msgstr "invalide unicode"
#: tiramisu/option.py:737
#: tiramisu/option.py:740
msgid "malformed symlinkoption must be an option for symlink {0}"
msgstr "symlinkoption mal formé, doit être une option pour symlink {0}"
#: tiramisu/option.py:787 tiramisu/option.py:792
#: tiramisu/option.py:791 tiramisu/option.py:794 tiramisu/option.py:799
msgid "invalid IP"
msgstr "adresse IP invalide"
#: tiramisu/option.py:797
msgid "invalid IP, mustn't not be in reserved class"
msgstr "adresse IP invalide, ne doit pas être d'une classe reservée"
#: tiramisu/option.py:805
msgid "IP is in reserved class"
msgstr "l'adresse IP est dans une plage d'adresse réservée"
#: tiramisu/option.py:799
#: tiramisu/option.py:807
msgid "invalid IP, mustn't be in reserved class"
msgstr "adresse IP invalide, ne doit pas être dans une classe réservée"
#: tiramisu/option.py:811
msgid "IP is not in private class"
msgstr "l'adresse IP n'est pas dans une plage d'adressage privée"
#: tiramisu/option.py:813
msgid "invalid IP, must be in private class"
msgstr "adresse IP invalide, doit être dans la classe privée"
#: tiramisu/option.py:837
msgid "inconsistency in allowed range"
msgstr "inconsistence dans la plage autorisée"
#: tiramisu/option.py:842
msgid "max value is empty"
msgstr "la valeur maximum est vide"
#: tiramisu/option.py:882
msgid "invalid network address"
msgstr "adresse réseau invalide"
#: tiramisu/option.py:887
msgid "invalid network address, must not be in reserved class"
msgstr "adresse réseau invalide, ne doit pas être dans la classe reservée"
#: tiramisu/option.py:899
msgid "invalid netmask address"
msgstr "masque de sous-réseau invalide"
#: tiramisu/option.py:915
msgid "invalid len for opts"
msgstr "longueur invalide pour opts"
#: tiramisu/option.py:927
msgid "invalid network {0} ({1}) with netmask {2}, this network is an IP"
msgstr "réseau invalide {0} ({1}) avec masque {2}, ce réseau est une IP"
#: tiramisu/option.py:932
msgid "invalid IP {0} ({1}) with netmask {2}, this IP is a network"
msgstr "IP invalide {0} ({1}) avec masque {2}, cette IP est un réseau"
#: tiramisu/option.py:937
msgid "invalid IP {0} ({1}) with netmask {2}"
msgstr "IP invalide {0} ({1}) avec masque {2}"
#: tiramisu/option.py:939
msgid "invalid network {0} ({1}) with netmask {2}"
msgstr "réseau invalide {0} ({1}) avec masque {2}"
#: tiramisu/option.py:953
msgid "invalid broadcast address"
msgstr "adresse de broadcast invalide"
#: tiramisu/option.py:957
#: tiramisu/option.py:818 tiramisu/option.py:993
msgid "invalid len for vals"
msgstr "longueur invalide pour vals"
#: tiramisu/option.py:962
#: tiramisu/option.py:824
msgid "IP {0} ({1}) not in network {2} ({3}) with netmask {4} ({5})"
msgstr "IP {0} ({1}) pas dans le réseau {2} ({3}) avec le masque {4} ({5})"
#: tiramisu/option.py:827
msgid "invalid IP {0} ({1}) not in network {2} ({3}) with netmask {4} ({5})"
msgstr ""
"IP invalide {0} ({1}) pas dans le réseau {2} ({3}) avec le masque {4} ({5})"
#: tiramisu/option.py:868
msgid "inconsistency in allowed range"
msgstr "inconsistence dans la plage autorisée"
#: tiramisu/option.py:873
msgid "max value is empty"
msgstr "la valeur maximum est vide"
#: tiramisu/option.py:890
msgid "invalid port, range must have two values only"
msgstr "port invalide, une plage doit avoir deux valeurs seulement"
#: tiramisu/option.py:893
msgid "invalid port, first port in range must be smaller than the second one"
msgstr ""
"port invalide, le premier port d'une plage doit être plus petit que le second"
#: tiramisu/option.py:902
msgid "invalid port"
msgstr "port invalide"
#: tiramisu/option.py:904
msgid "invalid port, must be an between {0} and {1}"
msgstr "port invalide, port doit être entre {0} et {1}"
#: tiramisu/option.py:918
msgid "invalid network address"
msgstr "adresse réseau invalide"
#: tiramisu/option.py:924
msgid "network address is in reserved class"
msgstr "l'adresse réseau est pas dans une plage d'adresse réservée"
#: tiramisu/option.py:926
msgid "invalid network address, mustn't be in reserved class"
msgstr "adresse réseau invalide, ne doit pas être dans la classe réservée"
#: tiramisu/option.py:939
msgid "invalid netmask address"
msgstr "masque de sous-réseau invalide"
#: tiramisu/option.py:956
msgid "invalid len for opts"
msgstr "longueur invalide pour opts"
#: tiramisu/option.py:970
msgid "invalid IP {0} ({1}) with netmask {2}, this IP is a network"
msgstr "IP invalide {0} ({1}) avec masque {2}, cette IP est un réseau"
#: tiramisu/option.py:975
msgid "invalid network {0} ({1}) with netmask {2}"
msgstr "réseau invalide {0} ({1}) avec masque {2}"
#: tiramisu/option.py:989
msgid "invalid broadcast address"
msgstr "adresse de broadcast invalide"
#: tiramisu/option.py:998
msgid ""
"invalid broadcast {0} ({1}) with network {2} ({3}) and netmask {4} ({5})"
msgstr ""
"Broadcast invalide {0} ({1}) avec le réseau {2} ({3}) et le masque {4} ({5})"
#: tiramisu/option.py:984
#: tiramisu/option.py:1020
msgid "unknown type_ {0} for hostname"
msgstr "type_ inconnu {0} pour le nom d'hôte"
#: tiramisu/option.py:987
#: tiramisu/option.py:1023
msgid "allow_ip must be a boolean"
msgstr "allow_ip doit être un booléen"
#: tiramisu/option.py:989
#: tiramisu/option.py:1025
msgid "allow_without_dot must be a boolean"
msgstr "allow_without_dot doit être un booléen"
#: tiramisu/option.py:1028
#: tiramisu/option.py:1069
msgid "invalid domainname, must have dot"
msgstr "nom de domaine invalide, doit avoir un point"
#: tiramisu/option.py:1030
#: tiramisu/option.py:1071
msgid "invalid domainname's length (max 255)"
msgstr "longueur du nom de domaine invalide (maximum {1})"
#: tiramisu/option.py:1032
#: tiramisu/option.py:1073
msgid "invalid domainname's length (min 2)"
msgstr "longueur du nom de domaine invalide (minimum 2)"
#: tiramisu/option.py:1034
#: tiramisu/option.py:1075
msgid "invalid domainname"
msgstr "nom de domaine invalide"
#: tiramisu/option.py:1047
msgid "invalid email address, should contains one @"
msgstr "adresse email invalide, devrait contenir un @"
#: tiramisu/option.py:1088
msgid "invalid email address, must contains one @"
msgstr "adresse email invalide, doit contenir un @"
#: tiramisu/option.py:1050
#: tiramisu/option.py:1091
msgid "invalid username in email address"
msgstr "nom d'utilisateur invalide dans une adresse email"
#: tiramisu/option.py:1063
msgid "invalid url, should start with http:// or https://"
msgstr "URL invalide, devrait démarré avec http:// ou https://"
#: tiramisu/option.py:1104
msgid "invalid url, must start with http:// or https://"
msgstr "URL invalide, doit démarrer avec http:// ou https://"
#: tiramisu/option.py:1082
#: tiramisu/option.py:1123
msgid "invalid url, port must be an between 0 and 65536"
msgstr "URL invalide, port doit être entre 0 et 65536"
#: tiramisu/option.py:1088
msgid "invalid url, should ends with filename"
msgstr "URL invalide, devrait finir avec un nom de fichier"
#: tiramisu/option.py:1129
msgid "invalid url, must ends with filename"
msgstr "URL invalide, doit finir avec un nom de fichier"
#: tiramisu/option.py:1099
#: tiramisu/option.py:1141
msgid "invalid username"
msgstr "utilisateur invalide"
#: tiramisu/option.py:1152
msgid "invalid filename"
msgstr "nom de fichier invalide"
#: tiramisu/option.py:1126
#: tiramisu/option.py:1179
msgid "duplicate option name: {0}"
msgstr "nom de l'option dupliqué : {0}"
#: tiramisu/option.py:1144
#: tiramisu/option.py:1197
msgid "unknown Option {0} in OptionDescription {1}"
msgstr "Option {0} inconnue pour l'OptionDescription {1}"
#: tiramisu/option.py:1195
#: tiramisu/option.py:1248
msgid "duplicate option: {0}"
msgstr "option dupliquée : {0}"
#: tiramisu/option.py:1225
#: tiramisu/option.py:1279
msgid "consistency with option {0} which is not in Config"
msgstr "consistency avec l'option {0} qui n'est pas dans une Config"
#: tiramisu/option.py:1233
#: tiramisu/option.py:1287
msgid "no option for path {0}"
msgstr "pas d'option pour le chemin {0}"
#: tiramisu/option.py:1239
#: tiramisu/option.py:1293
msgid "no option {0} found"
msgstr "pas d'option {0} trouvée"
#: tiramisu/option.py:1249
#: tiramisu/option.py:1303
msgid "cannot change group_type if already set (old {0}, new {1})"
msgstr "ne peut changer group_type si déjà spécifié (ancien {0}, nouveau {1})"
#: tiramisu/option.py:1261
#: tiramisu/option.py:1315
msgid "master group {0} shall not have a subgroup"
msgstr "groupe maître {0} ne doit pas avoir de sous-groupe"
#: tiramisu/option.py:1264
#: tiramisu/option.py:1318
msgid "master group {0} shall not have a symlinkoption"
msgstr "groupe maître {0} ne doit pas avoir de symlinkoption"
#: tiramisu/option.py:1267
#: tiramisu/option.py:1321
msgid "not allowed option {0} in group {1}: this option is not a multi"
msgstr ""
"option non autorisée {0} dans le groupe {1} : cette option n'est pas une "
"multi"
#: tiramisu/option.py:1277
#: tiramisu/option.py:1331
msgid "master group with wrong master name for {0}"
msgstr "le groupe maître avec un nom de maître érroné pour {0}"
#: tiramisu/option.py:1285
#: tiramisu/option.py:1339
msgid "callback of master's option shall not refered a slave's ones"
msgstr ""
"callback d'une variable maitre ne devrait pas référencer des variables "
"esclaves"
#: tiramisu/option.py:1293
#: tiramisu/option.py:1347
msgid "group_type: {0} not allowed"
msgstr "group_type : {0} non autorisé"
#: tiramisu/option.py:1385
#: tiramisu/option.py:1444
msgid "malformed requirements type for option: {0}, must be a dict"
msgstr ""
"type requirements malformé pour l'option : {0}, doit être un dictionnaire"
#: tiramisu/option.py:1402
#: tiramisu/option.py:1461
msgid ""
"malformed requirements for option: {0} require must have option, expected "
"and action keys"
@ -359,110 +420,127 @@ msgstr ""
"requirements malformé pour l'option : {0} l'exigence doit avoir les clefs "
"option, expected et action"
#: tiramisu/option.py:1407
#: tiramisu/option.py:1465
msgid ""
"malformed requirements for option: {0} action cannot be force_store_value"
msgstr ""
"requirements mal formés pour l'option : {0} action ne peut pas être "
"force_store_value"
#: tiramisu/option.py:1470
msgid "malformed requirements for option: {0} inverse must be boolean"
msgstr ""
"requirements mal formés pour l'option : {0} inverse doit être un booléen"
#: tiramisu/option.py:1411
#: tiramisu/option.py:1474
msgid "malformed requirements for option: {0} transitive must be boolean"
msgstr ""
"requirements mal formés pour l'option : {0} transitive doit être booléen"
#: tiramisu/option.py:1415
#: tiramisu/option.py:1478
msgid "malformed requirements for option: {0} same_action must be boolean"
msgstr ""
"requirements mal formés pour l'option : {0} same_action doit être un booléen"
#: tiramisu/option.py:1419
#: tiramisu/option.py:1482
msgid "malformed requirements must be an option in option {0}"
msgstr "requirements mal formés doit être une option dans l'option {0}"
#: tiramisu/option.py:1422
msgid "malformed requirements option {0} should not be a multi"
#: tiramisu/option.py:1485
msgid "malformed requirements option {0} must not be a multi"
msgstr "requirements mal formés l'option {0} ne doit pas être une multi"
#: tiramisu/option.py:1428
#: tiramisu/option.py:1491
msgid ""
"malformed requirements second argument must be valid for option {0}: {1}"
msgstr ""
"requirements mal formés deuxième argument doit être valide pour l'option "
"{0} : {1}"
#: tiramisu/option.py:1433
#: tiramisu/option.py:1496
msgid "inconsistency in action types for option: {0} action: {1}"
msgstr "incohérence dans les types action pour l'option : {0} action {1}"
#: tiramisu/option.py:1458
msgid "{0} should be a function"
#: tiramisu/option.py:1521
msgid "{0} must be a function"
msgstr "{0} doit être une fonction"
#: tiramisu/option.py:1461
msgid "{0}_params should be a dict"
msgstr "{0}_params devrait être un dict"
#: tiramisu/option.py:1524
msgid "{0}_params must be a dict"
msgstr "{0}_params doit être un dict"
#: tiramisu/option.py:1464
msgid "{0}_params with key {1} should not have length different to 1"
#: tiramisu/option.py:1527
msgid "{0}_params with key {1} mustn't have length different to 1"
msgstr ""
"{0}_params avec la clef {1} devrait ne pas avoir une longueur différent de 1"
"{0}_params avec la clef {1} ne doit pas avoir une longueur différent de 1"
#: tiramisu/option.py:1468
msgid "{0}_params should be tuple for key \"{1}\""
msgstr "{0}_params devrait être un tuple pour la clef \"{1}\""
#: tiramisu/option.py:1531
msgid "{0}_params must be tuple for key \"{1}\""
msgstr "{0}_params doit être un tuple pour la clef \"{1}\""
#: tiramisu/option.py:1474
#: tiramisu/option.py:1537
msgid "{0}_params with length of tuple as 1 must only have None as first value"
msgstr ""
"{0}_params avec un tuple de longueur 1 doit seulement avoir None comme "
"première valeur"
#: tiramisu/option.py:1541
msgid "{0}_params must only have 1 or 2 as length"
msgstr "{0}_params doit seulement avoir une longueur de 1 ou 2"
#: tiramisu/option.py:1546
msgid "validator not support tuple"
msgstr "validator n'accepte pas de tuple"
#: tiramisu/option.py:1477
msgid "{0}_params should have an option not a {0} for first argument"
msgstr "{0}_params devrait avoir une option pas un {0} pour premier argument"
#: tiramisu/option.py:1549
msgid "{0}_params must have an option not a {0} for first argument"
msgstr "{0}_params doit avoir une option pas un {0} pour premier argument"
#: tiramisu/option.py:1481
msgid "{0}_params should have a boolean not a {0} for second argument"
msgstr "{0}_params devrait avoir un boolean pas un {0} pour second argument"
#: tiramisu/option.py:1553
msgid "{0}_params must have a boolean not a {0} for second argument"
msgstr "{0}_params doit avoir un booléen pas un {0} pour second argument"
#: tiramisu/setting.py:116
#: tiramisu/setting.py:111
msgid "can't rebind {0}"
msgstr "ne peut redéfinir ({0})"
#: tiramisu/setting.py:121
#: tiramisu/setting.py:116
msgid "can't unbind {0}"
msgstr "ne peut supprimer ({0})"
#: tiramisu/setting.py:272
#: tiramisu/setting.py:267
msgid "cannot append {0} property for option {1}: this property is calculated"
msgstr ""
"ne peut ajouter la propriété {0} dans l'option {1}: cette propriété est "
"calculée"
#: tiramisu/setting.py:363
#: tiramisu/setting.py:358
msgid "opt and all_properties must not be set together in reset"
msgstr "opt et all_properties ne doit pas être renseigné ensemble dans reset"
#: tiramisu/setting.py:378
#: tiramisu/setting.py:376
msgid "if opt is not None, path should not be None in _getproperties"
msgstr ""
"si opt n'est pas None, path devrait ne pas être à None dans _getproperties"
#: tiramisu/setting.py:483
#: tiramisu/setting.py:486
msgid "cannot change the value for option {0} this option is frozen"
msgstr ""
"ne peut modifier la valeur de l'option {0} cette option n'est pas modifiable"
#: tiramisu/setting.py:489
#: tiramisu/setting.py:492
msgid "trying to access to an option named: {0} with properties {1}"
msgstr "tentative d'accès à une option nommée : {0} avec les propriétés {1}"
#: tiramisu/setting.py:507
#: tiramisu/setting.py:510
msgid "permissive must be a tuple"
msgstr "permissive doit être un tuple"
#: tiramisu/setting.py:514 tiramisu/value.py:315
#: tiramisu/setting.py:517 tiramisu/value.py:349
msgid "invalid generic owner {0}"
msgstr "invalide owner générique {0}"
#: tiramisu/setting.py:602
#: tiramisu/setting.py:605
msgid ""
"malformed requirements imbrication detected for option: '{0}' with "
"requirement on: '{1}'"
@ -470,77 +548,95 @@ msgstr ""
"imbrication de requirements mal formés detectée pour l'option : '{0}' avec "
"requirement sur : '{1}'"
#: tiramisu/setting.py:613
#: tiramisu/setting.py:616
msgid "option '{0}' has requirement's property error: {1} {2}"
msgstr "l'option '{0}' a une erreur de propriété pour le requirement : {1} {2}"
#: tiramisu/storage/__init__.py:52
#: tiramisu/storage/__init__.py:47
msgid "storage_type is already set, cannot rebind it"
msgstr "storage_type est déjà défini, impossible de le redéfinir"
#: tiramisu/storage/__init__.py:86
#: tiramisu/storage/__init__.py:81
msgid "option {0} not already exists in storage {1}"
msgstr "option {0} n'existe pas dans l'espace de stockage {1}"
#: tiramisu/storage/dictionary/storage.py:39
#: tiramisu/storage/dictionary/storage.py:37
msgid "dictionary storage cannot delete session"
msgstr ""
"impossible de supprimer une session dans un espace de stockage dictionary"
#: tiramisu/storage/dictionary/storage.py:50
#: tiramisu/storage/dictionary/storage.py:48
msgid "session already used"
msgstr "session déjà utilisée"
#: tiramisu/storage/dictionary/storage.py:52
#: tiramisu/storage/dictionary/storage.py:50
msgid "a dictionary cannot be persistent"
msgstr "un espace de stockage dictionary ne peut être persistant"
#: tiramisu/value.py:322
#: tiramisu/value.py:356
msgid "no value for {0} cannot change owner to {1}"
msgstr "pas de valeur pour {0} ne peut changer d'utilisateur pour {1}"
#: tiramisu/value.py:442
#: tiramisu/value.py:438
msgid "can force cache only if cache is actived in config"
msgstr ""
"peut force la mise en cache seulement si le cache est activé dans la config"
#: tiramisu/value.py:477
msgid "{0} is already a Multi "
msgstr "{0} est déjà une Multi"
#: tiramisu/value.py:513 tiramisu/value.py:577
msgid "invalid len for the slave: {0} which has {1} as master"
msgstr "longueur invalide pour une esclave : {0} qui a {1} comme maître"
#: tiramisu/value.py:466
msgid "invalid len for the master: {0} which has {1} as slave with greater len"
msgstr ""
"longueur invalide pour un maître : {0} qui a {1} une esclave avec une plus "
"grande longueur"
#: tiramisu/value.py:496
#: tiramisu/value.py:549
msgid "cannot append a value on a multi option {0} which is a slave"
msgstr "ne peut ajouter une valeur sur l'option multi {0} qui est une esclave"
#: tiramisu/value.py:535
#: tiramisu/value.py:587
msgid "cannot sort multi option {0} if master or slave"
msgstr "ne peut trier une option multi {0} pour une maître ou une esclave"
#: tiramisu/value.py:539
#: tiramisu/value.py:591
msgid "cmp is not permitted in python v3 or greater"
msgstr "cmp n'est pas permis en python v3 ou supérieure"
#: tiramisu/value.py:548
#: tiramisu/value.py:600
msgid "cannot reverse multi option {0} if master or slave"
msgstr "ne peut inverser une option multi {0} pour une maître ou une esclave"
#: tiramisu/value.py:556
#: tiramisu/value.py:608
msgid "cannot insert multi option {0} if master or slave"
msgstr "ne peut insérer une option multi {0} pour une maître ou une esclave"
#: tiramisu/value.py:564
#: tiramisu/value.py:616
msgid "cannot extend multi option {0} if master or slave"
msgstr "ne peut étendre une option multi {0} pour une maître ou une esclave"
#: tiramisu/value.py:575
#: tiramisu/value.py:627
msgid "invalid value {0} for option {1}: {2}"
msgstr "valeur invalide {0} pour l'option {1} : {2}"
#: tiramisu/value.py:593
#: tiramisu/value.py:645
msgid "cannot pop a value on a multi option {0} which is a slave"
msgstr "ne peut supprimer une valeur dans l'option multi {0} qui est esclave"
#~ msgid "option not in all_cons_opts"
#~ msgstr "option non présentante dans all_cons_opts"
#~ msgid "invalid network {0} ({1}) with netmask {2}, this network is an IP"
#~ msgstr "réseau invalide {0} ({1}) avec masque {2}, ce réseau est une IP"
#~ msgid "invalid IP {0} ({1}) with netmask {2}"
#~ msgstr "IP invalide {0} ({1}) avec masque {2}"
#~ msgid ""
#~ "invalid len for the master: {0} which has {1} as slave with greater len"
#~ msgstr ""
#~ "longueur invalide pour un maître : {0} qui a {1} une esclave avec une "
#~ "plus grande longueur"
#~ msgid ""
#~ "unable to carry out a calculation, option value with multi types must "
#~ "have same length for: {0}"
@ -588,17 +684,6 @@ msgstr "ne peut supprimer une valeur dans l'option multi {0} qui est esclave"
#~ msgid "invalid name: {0} for optiondescription"
#~ msgstr "nom invalide : {0} pour l'optiondescription"
#~ msgid "metaconfig's children must be config, not {0}"
#~ msgstr "enfants d'une metaconfig doit être une config, pas {0}"
#~ msgid "all config in metaconfig must have same optiondescription"
#~ msgstr ""
#~ "toutes les configs d'une metaconfig doivent avoir la même "
#~ "optiondescription"
#~ msgid "child has already a metaconfig's"
#~ msgstr "enfant a déjà une metaconfig"
#~ msgid "not allowed group_type : {0}"
#~ msgstr "group_type non autorisé : {0}"

View File

@ -5,7 +5,7 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2014-01-25 11:30+CET\n"
"POT-Creation-Date: 2014-03-29 19:01+CET\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,488 +15,564 @@ msgstr ""
"Generated-By: pygettext.py 1.5\n"
#: tiramisu/autolib.py:159
#: tiramisu/autolib.py:165
msgid "unable to carry out a calculation, option {0} has properties: {1} for: {2}"
msgstr ""
#: tiramisu/config.py:52
#: tiramisu/config.py:51
msgid "descr must be an optiondescription, not {0}"
msgstr ""
#: tiramisu/config.py:127
#: tiramisu/config.py:126
msgid "unknown group_type: {0}"
msgstr ""
#: tiramisu/config.py:166 tiramisu/setting.py:339 tiramisu/value.py:57
#: tiramisu/value.py:427
#: tiramisu/config.py:163 tiramisu/setting.py:334 tiramisu/value.py:55
#: tiramisu/value.py:500
msgid "the context does not exist anymore"
msgstr ""
#: tiramisu/config.py:171
msgid "no option description found for this config (may be metaconfig without meta)"
#: tiramisu/config.py:168
msgid "no option description found for this config (may be GroupConfig)"
msgstr ""
#: tiramisu/config.py:197
#: tiramisu/config.py:194
msgid "can't assign to an OptionDescription"
msgstr ""
#: tiramisu/config.py:330
#: tiramisu/config.py:324
msgid "unknown type_ type {0}for _find"
msgstr ""
#: tiramisu/config.py:369
#: tiramisu/config.py:363
msgid "no option found in config with these criteria"
msgstr ""
#: tiramisu/config.py:419
#: tiramisu/config.py:413
msgid "make_dict can't filtering with value without option"
msgstr ""
#: tiramisu/config.py:440
#: tiramisu/config.py:434
msgid "unexpected path {0}, should start with {1}"
msgstr ""
#: tiramisu/config.py:500
#: tiramisu/config.py:491
msgid "opt in getowner must be an option not {0}"
msgstr ""
#: tiramisu/option.py:67
#: tiramisu/config.py:535
msgid "cannot serialize Config with MetaConfig"
msgstr ""
#: tiramisu/config.py:549
msgid "this storage is not serialisable, could be a none persistent storage"
msgstr ""
#: tiramisu/config.py:612
msgid "metaconfig's children must be a list"
msgstr ""
#: tiramisu/config.py:706
msgid "metaconfig's children should be config, not {0}"
msgstr ""
#: tiramisu/config.py:710
msgid "child has already a metaconfig's"
msgstr ""
#: tiramisu/config.py:714
msgid "all config in metaconfig must have the same optiondescription"
msgstr ""
#: tiramisu/option.py:66
msgid "invalid name: {0} for option"
msgstr ""
#: tiramisu/option.py:76
#: tiramisu/option.py:75
msgid "invalid properties type {0} for {1}, must be a tuple"
msgstr ""
#: tiramisu/option.py:114
#: tiramisu/option.py:113
msgid "'{0}' ({1}) object attribute '{2}' is read-only"
msgstr ""
#: tiramisu/option.py:141 tiramisu/value.py:376
#: tiramisu/option.py:140 tiramisu/value.py:410
msgid "information's item not found: {0}"
msgstr ""
#: tiramisu/option.py:203
#: tiramisu/option.py:202
msgid "cannot serialize Option, only in OptionDescription"
msgstr ""
#: tiramisu/option.py:306
#: tiramisu/option.py:305
msgid "a default_multi is set whereas multi is False in option: {0}"
msgstr ""
#: tiramisu/option.py:312
#: tiramisu/option.py:311
msgid "invalid default_multi value {0} for option {1}: {2}"
msgstr ""
#: tiramisu/option.py:317
#: tiramisu/option.py:316
msgid "default value not allowed if option: {0} is calculated"
msgstr ""
#: tiramisu/option.py:320
#: tiramisu/option.py:319
msgid "params defined for a callback function but no callback defined yet for option {0}"
msgstr ""
#: tiramisu/option.py:359
msgid "option not in all_cons_opts"
msgstr ""
#: tiramisu/option.py:425 tiramisu/option.py:435
#: tiramisu/option.py:424 tiramisu/option.py:454
msgid "invalid value for option {0}: {1}"
msgstr ""
#: tiramisu/option.py:452
#: tiramisu/option.py:448
msgid "warning on the value of the option {0}: {1}"
msgstr ""
#: tiramisu/option.py:465
msgid "invalid value {0} for option {1} which must be a list"
msgstr ""
#: tiramisu/option.py:508
msgid "consistency should be set with an option"
#: tiramisu/option.py:523
msgid "consistency must be set with an option"
msgstr ""
#: tiramisu/option.py:510
#: tiramisu/option.py:525
msgid "cannot add consistency with itself"
msgstr ""
#: tiramisu/option.py:512
msgid "every options in consistency should be multi or none"
#: tiramisu/option.py:527
msgid "every options in consistency must be multi or none"
msgstr ""
#: tiramisu/option.py:532
msgid "same value for {0} and {1}"
#: tiramisu/option.py:548
msgid "same value for {0} and {1}, should be different"
msgstr ""
#: tiramisu/option.py:641
msgid "values must be a tuple for {0}"
#: tiramisu/option.py:550
msgid "same value for {0} and {1}, must be different"
msgstr ""
#: tiramisu/option.py:644
msgid "values must be a tuple for {0}"
msgstr ""
#: tiramisu/option.py:647
msgid "open_values must be a boolean for {0}"
msgstr ""
#: tiramisu/option.py:666
#: tiramisu/option.py:669
msgid "value {0} is not permitted, only {1} is allowed"
msgstr ""
#: tiramisu/option.py:678
#: tiramisu/option.py:681
msgid "invalid boolean"
msgstr ""
#: tiramisu/option.py:688
#: tiramisu/option.py:691
msgid "invalid integer"
msgstr ""
#: tiramisu/option.py:698
#: tiramisu/option.py:701
msgid "invalid float"
msgstr ""
#: tiramisu/option.py:708
#: tiramisu/option.py:711
msgid "invalid string"
msgstr ""
#: tiramisu/option.py:725
#: tiramisu/option.py:728
msgid "invalid unicode"
msgstr ""
#: tiramisu/option.py:737
#: tiramisu/option.py:740
msgid "malformed symlinkoption must be an option for symlink {0}"
msgstr ""
#: tiramisu/option.py:787 tiramisu/option.py:792
#: tiramisu/option.py:791 tiramisu/option.py:794 tiramisu/option.py:799
msgid "invalid IP"
msgstr ""
#: tiramisu/option.py:797
msgid "invalid IP, mustn't not be in reserved class"
#: tiramisu/option.py:805
msgid "IP is in reserved class"
msgstr ""
#: tiramisu/option.py:799
#: tiramisu/option.py:807
msgid "invalid IP, mustn't be in reserved class"
msgstr ""
#: tiramisu/option.py:811
msgid "IP is not in private class"
msgstr ""
#: tiramisu/option.py:813
msgid "invalid IP, must be in private class"
msgstr ""
#: tiramisu/option.py:837
msgid "inconsistency in allowed range"
msgstr ""
#: tiramisu/option.py:842
msgid "max value is empty"
msgstr ""
#: tiramisu/option.py:882
msgid "invalid network address"
msgstr ""
#: tiramisu/option.py:887
msgid "invalid network address, must not be in reserved class"
msgstr ""
#: tiramisu/option.py:899
msgid "invalid netmask address"
msgstr ""
#: tiramisu/option.py:915
msgid "invalid len for opts"
msgstr ""
#: tiramisu/option.py:927
msgid "invalid network {0} ({1}) with netmask {2}, this network is an IP"
msgstr ""
#: tiramisu/option.py:932
msgid "invalid IP {0} ({1}) with netmask {2}, this IP is a network"
msgstr ""
#: tiramisu/option.py:937
msgid "invalid IP {0} ({1}) with netmask {2}"
msgstr ""
#: tiramisu/option.py:939
msgid "invalid network {0} ({1}) with netmask {2}"
msgstr ""
#: tiramisu/option.py:953
msgid "invalid broadcast address"
msgstr ""
#: tiramisu/option.py:957
#: tiramisu/option.py:818 tiramisu/option.py:993
msgid "invalid len for vals"
msgstr ""
#: tiramisu/option.py:962
msgid "invalid broadcast {0} ({1}) with network {2} ({3}) and netmask {4} ({5})"
#: tiramisu/option.py:824
msgid "IP {0} ({1}) not in network {2} ({3}) with netmask {4} ({5})"
msgstr ""
#: tiramisu/option.py:984
msgid "unknown type_ {0} for hostname"
#: tiramisu/option.py:827
msgid "invalid IP {0} ({1}) not in network {2} ({3}) with netmask {4} ({5})"
msgstr ""
#: tiramisu/option.py:987
msgid "allow_ip must be a boolean"
#: tiramisu/option.py:868
msgid "inconsistency in allowed range"
msgstr ""
#: tiramisu/option.py:873
msgid "max value is empty"
msgstr ""
#: tiramisu/option.py:890
msgid "invalid port, range must have two values only"
msgstr ""
#: tiramisu/option.py:893
msgid "invalid port, first port in range must be smaller than the second one"
msgstr ""
#: tiramisu/option.py:902
msgid "invalid port"
msgstr ""
#: tiramisu/option.py:904
msgid "invalid port, must be an between {0} and {1}"
msgstr ""
#: tiramisu/option.py:918
msgid "invalid network address"
msgstr ""
#: tiramisu/option.py:924
msgid "network address is in reserved class"
msgstr ""
#: tiramisu/option.py:926
msgid "invalid network address, mustn't be in reserved class"
msgstr ""
#: tiramisu/option.py:939
msgid "invalid netmask address"
msgstr ""
#: tiramisu/option.py:956
msgid "invalid len for opts"
msgstr ""
#: tiramisu/option.py:970
msgid "invalid IP {0} ({1}) with netmask {2}, this IP is a network"
msgstr ""
#: tiramisu/option.py:975
msgid "invalid network {0} ({1}) with netmask {2}"
msgstr ""
#: tiramisu/option.py:989
msgid "invalid broadcast address"
msgstr ""
#: tiramisu/option.py:998
msgid "invalid broadcast {0} ({1}) with network {2} ({3}) and netmask {4} ({5})"
msgstr ""
#: tiramisu/option.py:1020
msgid "unknown type_ {0} for hostname"
msgstr ""
#: tiramisu/option.py:1023
msgid "allow_ip must be a boolean"
msgstr ""
#: tiramisu/option.py:1025
msgid "allow_without_dot must be a boolean"
msgstr ""
#: tiramisu/option.py:1028
#: tiramisu/option.py:1069
msgid "invalid domainname, must have dot"
msgstr ""
#: tiramisu/option.py:1030
#: tiramisu/option.py:1071
msgid "invalid domainname's length (max 255)"
msgstr ""
#: tiramisu/option.py:1032
#: tiramisu/option.py:1073
msgid "invalid domainname's length (min 2)"
msgstr ""
#: tiramisu/option.py:1034
#: tiramisu/option.py:1075
msgid "invalid domainname"
msgstr ""
#: tiramisu/option.py:1047
msgid "invalid email address, should contains one @"
#: tiramisu/option.py:1088
msgid "invalid email address, must contains one @"
msgstr ""
#: tiramisu/option.py:1050
#: tiramisu/option.py:1091
msgid "invalid username in email address"
msgstr ""
#: tiramisu/option.py:1063
msgid "invalid url, should start with http:// or https://"
#: tiramisu/option.py:1104
msgid "invalid url, must start with http:// or https://"
msgstr ""
#: tiramisu/option.py:1082
#: tiramisu/option.py:1123
msgid "invalid url, port must be an between 0 and 65536"
msgstr ""
#: tiramisu/option.py:1088
msgid "invalid url, should ends with filename"
#: tiramisu/option.py:1129
msgid "invalid url, must ends with filename"
msgstr ""
#: tiramisu/option.py:1099
#: tiramisu/option.py:1141
msgid "invalid username"
msgstr ""
#: tiramisu/option.py:1152
msgid "invalid filename"
msgstr ""
#: tiramisu/option.py:1126
#: tiramisu/option.py:1179
msgid "duplicate option name: {0}"
msgstr ""
#: tiramisu/option.py:1144
#: tiramisu/option.py:1197
msgid "unknown Option {0} in OptionDescription {1}"
msgstr ""
#: tiramisu/option.py:1195
#: tiramisu/option.py:1248
msgid "duplicate option: {0}"
msgstr ""
#: tiramisu/option.py:1225
#: tiramisu/option.py:1279
msgid "consistency with option {0} which is not in Config"
msgstr ""
#: tiramisu/option.py:1233
#: tiramisu/option.py:1287
msgid "no option for path {0}"
msgstr ""
#: tiramisu/option.py:1239
#: tiramisu/option.py:1293
msgid "no option {0} found"
msgstr ""
#: tiramisu/option.py:1249
#: tiramisu/option.py:1303
msgid "cannot change group_type if already set (old {0}, new {1})"
msgstr ""
#: tiramisu/option.py:1261
#: tiramisu/option.py:1315
msgid "master group {0} shall not have a subgroup"
msgstr ""
#: tiramisu/option.py:1264
#: tiramisu/option.py:1318
msgid "master group {0} shall not have a symlinkoption"
msgstr ""
#: tiramisu/option.py:1267
#: tiramisu/option.py:1321
msgid "not allowed option {0} in group {1}: this option is not a multi"
msgstr ""
#: tiramisu/option.py:1277
#: tiramisu/option.py:1331
msgid "master group with wrong master name for {0}"
msgstr ""
#: tiramisu/option.py:1285
#: tiramisu/option.py:1339
msgid "callback of master's option shall not refered a slave's ones"
msgstr ""
#: tiramisu/option.py:1293
#: tiramisu/option.py:1347
msgid "group_type: {0} not allowed"
msgstr ""
#: tiramisu/option.py:1385
#: tiramisu/option.py:1444
msgid "malformed requirements type for option: {0}, must be a dict"
msgstr ""
#: tiramisu/option.py:1402
#: tiramisu/option.py:1461
msgid "malformed requirements for option: {0} require must have option, expected and action keys"
msgstr ""
#: tiramisu/option.py:1407
#: tiramisu/option.py:1465
msgid "malformed requirements for option: {0} action cannot be force_store_value"
msgstr ""
#: tiramisu/option.py:1470
msgid "malformed requirements for option: {0} inverse must be boolean"
msgstr ""
#: tiramisu/option.py:1411
#: tiramisu/option.py:1474
msgid "malformed requirements for option: {0} transitive must be boolean"
msgstr ""
#: tiramisu/option.py:1415
#: tiramisu/option.py:1478
msgid "malformed requirements for option: {0} same_action must be boolean"
msgstr ""
#: tiramisu/option.py:1419
#: tiramisu/option.py:1482
msgid "malformed requirements must be an option in option {0}"
msgstr ""
#: tiramisu/option.py:1422
msgid "malformed requirements option {0} should not be a multi"
#: tiramisu/option.py:1485
msgid "malformed requirements option {0} must not be a multi"
msgstr ""
#: tiramisu/option.py:1428
#: tiramisu/option.py:1491
msgid "malformed requirements second argument must be valid for option {0}: {1}"
msgstr ""
#: tiramisu/option.py:1433
#: tiramisu/option.py:1496
msgid "inconsistency in action types for option: {0} action: {1}"
msgstr ""
#: tiramisu/option.py:1458
msgid "{0} should be a function"
#: tiramisu/option.py:1521
msgid "{0} must be a function"
msgstr ""
#: tiramisu/option.py:1461
msgid "{0}_params should be a dict"
#: tiramisu/option.py:1524
msgid "{0}_params must be a dict"
msgstr ""
#: tiramisu/option.py:1464
msgid "{0}_params with key {1} should not have length different to 1"
#: tiramisu/option.py:1527
msgid "{0}_params with key {1} mustn't have length different to 1"
msgstr ""
#: tiramisu/option.py:1468
msgid "{0}_params should be tuple for key \"{1}\""
#: tiramisu/option.py:1531
msgid "{0}_params must be tuple for key \"{1}\""
msgstr ""
#: tiramisu/option.py:1474
#: tiramisu/option.py:1537
msgid "{0}_params with length of tuple as 1 must only have None as first value"
msgstr ""
#: tiramisu/option.py:1541
msgid "{0}_params must only have 1 or 2 as length"
msgstr ""
#: tiramisu/option.py:1546
msgid "validator not support tuple"
msgstr ""
#: tiramisu/option.py:1477
msgid "{0}_params should have an option not a {0} for first argument"
#: tiramisu/option.py:1549
msgid "{0}_params must have an option not a {0} for first argument"
msgstr ""
#: tiramisu/option.py:1481
msgid "{0}_params should have a boolean not a {0} for second argument"
#: tiramisu/option.py:1553
msgid "{0}_params must have a boolean not a {0} for second argument"
msgstr ""
#: tiramisu/setting.py:116
#: tiramisu/setting.py:111
msgid "can't rebind {0}"
msgstr ""
#: tiramisu/setting.py:121
#: tiramisu/setting.py:116
msgid "can't unbind {0}"
msgstr ""
#: tiramisu/setting.py:272
#: tiramisu/setting.py:267
msgid "cannot append {0} property for option {1}: this property is calculated"
msgstr ""
#: tiramisu/setting.py:363
#: tiramisu/setting.py:358
msgid "opt and all_properties must not be set together in reset"
msgstr ""
#: tiramisu/setting.py:378
#: tiramisu/setting.py:376
msgid "if opt is not None, path should not be None in _getproperties"
msgstr ""
#: tiramisu/setting.py:483
#: tiramisu/setting.py:486
msgid "cannot change the value for option {0} this option is frozen"
msgstr ""
#: tiramisu/setting.py:489
#: tiramisu/setting.py:492
msgid "trying to access to an option named: {0} with properties {1}"
msgstr ""
#: tiramisu/setting.py:507
#: tiramisu/setting.py:510
msgid "permissive must be a tuple"
msgstr ""
#: tiramisu/setting.py:514 tiramisu/value.py:315
#: tiramisu/setting.py:517 tiramisu/value.py:349
msgid "invalid generic owner {0}"
msgstr ""
#: tiramisu/setting.py:602
#: tiramisu/setting.py:605
msgid "malformed requirements imbrication detected for option: '{0}' with requirement on: '{1}'"
msgstr ""
#: tiramisu/setting.py:613
#: tiramisu/setting.py:616
msgid "option '{0}' has requirement's property error: {1} {2}"
msgstr ""
#: tiramisu/storage/__init__.py:52
#: tiramisu/storage/__init__.py:47
msgid "storage_type is already set, cannot rebind it"
msgstr ""
#: tiramisu/storage/__init__.py:86
#: tiramisu/storage/__init__.py:81
msgid "option {0} not already exists in storage {1}"
msgstr ""
#: tiramisu/storage/dictionary/storage.py:39
#: tiramisu/storage/dictionary/storage.py:37
msgid "dictionary storage cannot delete session"
msgstr ""
#: tiramisu/storage/dictionary/storage.py:50
#: tiramisu/storage/dictionary/storage.py:48
msgid "session already used"
msgstr ""
#: tiramisu/storage/dictionary/storage.py:52
#: tiramisu/storage/dictionary/storage.py:50
msgid "a dictionary cannot be persistent"
msgstr ""
#: tiramisu/value.py:322
#: tiramisu/value.py:356
msgid "no value for {0} cannot change owner to {1}"
msgstr ""
#: tiramisu/value.py:442
#: tiramisu/value.py:438
msgid "can force cache only if cache is actived in config"
msgstr ""
#: tiramisu/value.py:477
msgid "{0} is already a Multi "
msgstr ""
#: tiramisu/value.py:513 tiramisu/value.py:577
msgid "invalid len for the slave: {0} which has {1} as master"
msgstr ""
#: tiramisu/value.py:466
msgid "invalid len for the master: {0} which has {1} as slave with greater len"
msgstr ""
#: tiramisu/value.py:496
#: tiramisu/value.py:549
msgid "cannot append a value on a multi option {0} which is a slave"
msgstr ""
#: tiramisu/value.py:535
#: tiramisu/value.py:587
msgid "cannot sort multi option {0} if master or slave"
msgstr ""
#: tiramisu/value.py:539
#: tiramisu/value.py:591
msgid "cmp is not permitted in python v3 or greater"
msgstr ""
#: tiramisu/value.py:548
#: tiramisu/value.py:600
msgid "cannot reverse multi option {0} if master or slave"
msgstr ""
#: tiramisu/value.py:556
#: tiramisu/value.py:608
msgid "cannot insert multi option {0} if master or slave"
msgstr ""
#: tiramisu/value.py:564
#: tiramisu/value.py:616
msgid "cannot extend multi option {0} if master or slave"
msgstr ""
#: tiramisu/value.py:575
#: tiramisu/value.py:627
msgid "invalid value {0} for option {1}: {2}"
msgstr ""
#: tiramisu/value.py:593
#: tiramisu/value.py:645
msgid "cannot pop a value on a multi option {0} which is a slave"
msgstr ""