diff --git a/COPYING b/COPYING index 58ed914..1db1c0c 100644 --- a/COPYING +++ b/COPYING @@ -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. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..e89d792 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,13 @@ +Sat Apr 12 11:37:27 CEST 2014 Emmanuel Garette + + * 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 diff --git a/VERSION b/VERSION index 0f82de4..1f7391f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0rc1 +master diff --git a/doc/index.txt b/doc/index.txt index 1a55ca4..51972bd 100644 --- a/doc/index.txt +++ b/doc/index.txt @@ -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:: diff --git a/gpl-3.0.txt b/gpl-3.0.txt deleted file mode 100644 index 818433e..0000000 --- a/gpl-3.0.txt +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - 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. - - - Copyright (C) - - 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 . - -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: - - Copyright (C) - 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 -. - - 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 -. diff --git a/lgpl-3.0.txt b/lgpl-3.0.txt new file mode 100644 index 0000000..65c5ca8 --- /dev/null +++ b/lgpl-3.0.txt @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + 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. diff --git a/setup.py b/setup.py index 1e67a75..de30995 100644 --- a/setup.py +++ b/setup.py @@ -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" diff --git a/test/test_cache.py b/test/test_cache.py index f483c76..e1cd609 100644 --- a/test/test_cache.py +++ b/test/test_cache.py @@ -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()") diff --git a/test/test_config.py b/test/test_config.py index 7bc9cf5..9547722 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -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() diff --git a/test/test_config_api.py b/test/test_config_api.py index 75c0b80..979a1e8 100644 --- a/test/test_config_api.py +++ b/test/test_config_api.py @@ -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") diff --git a/test/test_config_domain.py b/test/test_config_domain.py index 8fcf1f1..bcae92a 100644 --- a/test/test_config_domain.py +++ b/test/test_config_domain.py @@ -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') diff --git a/test/test_freeze.py b/test/test_freeze.py index cda1406..21b555b 100644 --- a/test/test_freeze.py +++ b/test/test_freeze.py @@ -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)} diff --git a/test/test_mandatory.py b/test/test_mandatory.py index c535e31..59da145 100644 --- a/test/test_mandatory.py +++ b/test/test_mandatory.py @@ -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) diff --git a/test/test_option_calculation.py b/test/test_option_calculation.py index 34886c6..3199308 100644 --- a/test/test_option_calculation.py +++ b/test/test_option_calculation.py @@ -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) diff --git a/test/test_option_consistency.py b/test/test_option_consistency.py index 6c736c1..5a81e56 100644 --- a/test/test_option_consistency.py +++ b/test/test_option_consistency.py @@ -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 diff --git a/test/test_option_owner.py b/test/test_option_owner.py index d4c3f6b..1880bc4 100644 --- a/test/test_option_owner.py +++ b/test/test_option_owner.py @@ -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]) diff --git a/test/test_option_setting.py b/test/test_option_setting.py index e857a8b..cd38a48 100644 --- a/test/test_option_setting.py +++ b/test/test_option_setting.py @@ -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']" diff --git a/test/test_option_username.py b/test/test_option_username.py new file mode 100644 index 0000000..472e9cd --- /dev/null +++ b/test/test_option_username.py @@ -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$')") diff --git a/test/test_option_validator.py b/test/test_option_validator.py index de6391a..c997f21 100644 --- a/test/test_option_validator.py +++ b/test/test_option_validator.py @@ -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') diff --git a/test/test_parsing_group.py b/test/test_parsing_group.py index c3b6ffc..46bd82d 100644 --- a/test/test_parsing_group.py +++ b/test/test_parsing_group.py @@ -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) diff --git a/test/test_requires.py b/test/test_requires.py index ff9c2a6..c6d1032 100644 --- a/test/test_requires.py +++ b/test/test_requires.py @@ -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) diff --git a/tiramisu/__init__.py b/tiramisu/__init__.py index e69de29..f5f3f32 100644 --- a/tiramisu/__init__.py +++ b/tiramisu/__init__.py @@ -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 . diff --git a/tiramisu/autolib.py b/tiramisu/autolib.py index b2b07c9..3e3eea7 100644 --- a/tiramisu/autolib.py +++ b/tiramisu/autolib.py @@ -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 . # # 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: diff --git a/tiramisu/config.py b/tiramisu/config.py index abc3261..9c0ac2a 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -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 . # # 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() diff --git a/tiramisu/error.py b/tiramisu/error.py index 5694e4d..6a7c8be 100644 --- a/tiramisu/error.py +++ b/tiramisu/error.py @@ -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 . # ____________________________________________________________ "user defined exceptions" diff --git a/tiramisu/i18n.py b/tiramisu/i18n.py index a1fd01a..0223ccf 100644 --- a/tiramisu/i18n.py +++ b/tiramisu/i18n.py @@ -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 . +# +# 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 diff --git a/tiramisu/option.py b/tiramisu/option.py deleted file mode 100644 index fa0420a..0000000 --- a/tiramisu/option.py +++ /dev/null @@ -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))) diff --git a/tiramisu/option/__init__.py b/tiramisu/option/__init__.py new file mode 100644 index 0000000..1a325f8 --- /dev/null +++ b/tiramisu/option/__init__.py @@ -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) diff --git a/tiramisu/option/baseoption.py b/tiramisu/option/baseoption.py new file mode 100644 index 0000000..25c5e11 --- /dev/null +++ b/tiramisu/option/baseoption.py @@ -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 . +# +# 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) diff --git a/tiramisu/option/masterslave.py b/tiramisu/option/masterslave.py new file mode 100644 index 0000000..4298cc7 --- /dev/null +++ b/tiramisu/option/masterslave.py @@ -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 . +# +# 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 diff --git a/tiramisu/option/option.py b/tiramisu/option/option.py new file mode 100644 index 0000000..d707940 --- /dev/null +++ b/tiramisu/option/option.py @@ -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 . +# +# 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')) diff --git a/tiramisu/option/optiondescription.py b/tiramisu/option/optiondescription.py new file mode 100644 index 0000000..e692458 --- /dev/null +++ b/tiramisu/option/optiondescription.py @@ -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 . +# +# 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() diff --git a/tiramisu/setting.py b/tiramisu/setting.py index fc4241d..887c16c 100644 --- a/tiramisu/setting.py +++ b/tiramisu/setting.py @@ -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 . # ____________________________________________________________ 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)} diff --git a/tiramisu/storage/__init__.py b/tiramisu/storage/__init__.py index 30e2057..4763bad 100644 --- a/tiramisu/storage/__init__.py +++ b/tiramisu/storage/__init__.py @@ -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 . # ____________________________________________________________ """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_): diff --git a/tiramisu/storage/dictionary/__init__.py b/tiramisu/storage/dictionary/__init__.py index 3a7b186..e6f358d 100644 --- a/tiramisu/storage/dictionary/__init__.py +++ b/tiramisu/storage/dictionary/__init__.py @@ -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 . # ____________________________________________________________ """Default plugin for storage. All informations are store in a simple dictionary in memory. diff --git a/tiramisu/storage/dictionary/setting.py b/tiramisu/storage/dictionary/setting.py index 899af4a..a25c989 100644 --- a/tiramisu/storage/dictionary/setting.py +++ b/tiramisu/storage/dictionary/setting.py @@ -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 . # ____________________________________________________________ from ..util import Cache diff --git a/tiramisu/storage/dictionary/storage.py b/tiramisu/storage/dictionary/storage.py index 465fe26..702ded4 100644 --- a/tiramisu/storage/dictionary/storage.py +++ b/tiramisu/storage/dictionary/storage.py @@ -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 . # ____________________________________________________________ from tiramisu.i18n import _ from tiramisu.error import ConfigError diff --git a/tiramisu/storage/dictionary/value.py b/tiramisu/storage/dictionary/value.py index fedf1ec..bf3196c 100644 --- a/tiramisu/storage/dictionary/value.py +++ b/tiramisu/storage/dictionary/value.py @@ -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 . # ____________________________________________________________ from ..util import Cache diff --git a/tiramisu/storage/sqlite3/__init__.py b/tiramisu/storage/sqlite3/__init__.py index 8d79070..4f9754b 100644 --- a/tiramisu/storage/sqlite3/__init__.py +++ b/tiramisu/storage/sqlite3/__init__.py @@ -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 . # ____________________________________________________________ """Sqlite3 plugin for storage. This storage is not made to be used in productive environment. It was developing as proof of concept. diff --git a/tiramisu/storage/sqlite3/setting.py b/tiramisu/storage/sqlite3/setting.py index 9a5d2f9..a4478d2 100644 --- a/tiramisu/storage/sqlite3/setting.py +++ b/tiramisu/storage/sqlite3/setting.py @@ -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 . # ____________________________________________________________ from .sqlite3db import Sqlite3DB diff --git a/tiramisu/storage/sqlite3/sqlite3db.py b/tiramisu/storage/sqlite3/sqlite3db.py index 68f2886..2b33117 100644 --- a/tiramisu/storage/sqlite3/sqlite3db.py +++ b/tiramisu/storage/sqlite3/sqlite3db.py @@ -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 . # ____________________________________________________________ try: from cPickle import loads, dumps diff --git a/tiramisu/storage/sqlite3/storage.py b/tiramisu/storage/sqlite3/storage.py index 3b4f265..b441466 100644 --- a/tiramisu/storage/sqlite3/storage.py +++ b/tiramisu/storage/sqlite3/storage.py @@ -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 . # ____________________________________________________________ from os import unlink diff --git a/tiramisu/storage/sqlite3/value.py b/tiramisu/storage/sqlite3/value.py index 672ecab..eaa434f 100644 --- a/tiramisu/storage/sqlite3/value.py +++ b/tiramisu/storage/sqlite3/value.py @@ -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 . # ____________________________________________________________ from .sqlite3db import Sqlite3DB diff --git a/tiramisu/storage/util.py b/tiramisu/storage/util.py index 88cf787..1657f18 100644 --- a/tiramisu/storage/util.py +++ b/tiramisu/storage/util.py @@ -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 . # ____________________________________________________________ from tiramisu.setting import owners diff --git a/tiramisu/value.py b/tiramisu/value.py index a2e640c..4700e8e 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -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 . # ____________________________________________________________ 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 diff --git a/translations/fr/tiramisu.po b/translations/fr/tiramisu.po index 83a9289..eb14d72 100644 --- a/translations/fr/tiramisu.po +++ b/translations/fr/tiramisu.po @@ -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 \n" "Language-Team: Tiramisu's team \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}" diff --git a/translations/tiramisu.pot b/translations/tiramisu.pot index e572a40..35ecafe 100644 --- a/translations/tiramisu.pot +++ b/translations/tiramisu.pot @@ -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 \n" "Language-Team: LANGUAGE \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 ""