apptools-4.1.0/0000755000175100001440000000000011755040151014335 5ustar ischnellusers00000000000000apptools-4.1.0/TODO.txt0000644000175100001440000000126611674464005015660 0ustar ischnellusers00000000000000* Take a critical look at whether apptools.template should be in this project. It appears to be adding a lot of dependencies the other packages do not need. For now, its use is controlled through an extra. * Refactor the state_pickler.py module to break the need to import tvtk at all. * Check to see if the pyface.resource package is redundant with the envisage.resource code. If so, let's minimize to one location. Note that, at least as of Jan 2008, we are considering moving pyface.resource into the TraitsGUI project to resolve issues with dependencies there. * Port apptools.help to envisage2, currently it relies on the old envisage, and is excluded from the tests. apptools-4.1.0/integrationtests/0000755000175100001440000000000011674464005017753 5ustar ischnellusers00000000000000apptools-4.1.0/integrationtests/persistence/0000755000175100001440000000000011674464005022277 5ustar ischnellusers00000000000000apptools-4.1.0/integrationtests/persistence/update2.py0000644000175100001440000000106611674464005024220 0ustar ischnellusers00000000000000# Update class names from the immediately prior version only # to ensure that cycles are not possible from apptools.persistence.updater import Updater def update_project(self, state): print 'updating to v2' metadata = state['metadata'] metadata['version'] = 2 metadata['updater'] = 22 return state class Update2(Updater): def __init__(self): self.refactorings = { ("__main__", "Foo1"): ("__main__", "Foo2"), } self.setstates = { ("cplab.project", "Project"): update_project } apptools-4.1.0/integrationtests/persistence/test_persistence.py0000644000175100001440000000432311674464005026236 0ustar ischnellusers00000000000000class Foo0: """ The original class written with no expectation of being upgraded """ def __init__(self): self.prenom = 'didier' self.surnom = 'enfant' class Foo1: """ Now to handle both Foo v0 and Foo v1 we need to add more code ...""" def __init__(self, firstname, lastname): """ This does not get called when the class is unpickled.""" self.firstname = firstname self.lastname = lastname class Foo: def __str__(self): result = ['-----------------------------------------------------------'] keys = dir(self) for key in keys: result.append('%s ---> %s' % (key, getattr(self, key))) result.append('-----------------------------------------------------------') return '\n'.join(result) def __setstate__(self, state): print 'calling setstate on the real Foo' state['set'] = True self.__dict__.update(state) def save(fname, str): f=open(fname, 'w') f.write(str) f.close() return if __name__ == '__main__': # Create dummy test data ....... #from cStringIO import StringIO import pickle obj = Foo0() print obj t0 = pickle.dumps(obj) #.replace('Foo0', 'Foo') save('foo0.txt', t0) '''obj = Foo1('duncan', 'child') t1 = pickle.dumps(obj).replace('Foo1', 'Foo') save('foo1.txt', t1) obj = Foo2('duncan child') t2 = pickle.dumps(obj).replace('Foo2', 'Foo') save('foo2.txt', t2) obj = Foo3('duncan child') t3 = pickle.dumps(obj).replace('Foo3', 'Foo') save('foo3.txt', t3) ''' print '====================================================================' from apptools.persistence.versioned_unpickler import VersionedUnpickler from update1 import Update1 # Try and read them back in ... f = open('foo0.txt') import sys rev = 1 __import__('integrationtests.persistence.update%d' % rev) mod = sys.modules['integrationtests.persistence.update%d' % rev] klass = getattr(mod, 'Update%d' % rev) updater = klass() print '%s %s' % (rev, updater) p = VersionedUnpickler(f, updater).load() print p print 'Restored version %s %s' % (p.lastname, p.firstname) #print p.set apptools-4.1.0/integrationtests/persistence/update3.py0000644000175100001440000000106711674464005024222 0ustar ischnellusers00000000000000# Update class names from the immediately prior version only # to ensure that cycles are not possible from apptools.persistence.updater import Updater def update_project(self, state): print 'updating to v3' metadata = state['metadata'] metadata['version'] = 3 metadata['finished'] = True return state class Update3(Updater): def __init__(self): self.refactorings = { ("__main__", "Foo1"): ("__main__", "Foo2"), } self.setstates = { ("cplab.project", "Project"): update_project } apptools-4.1.0/integrationtests/persistence/update1.py0000644000175100001440000000162711674464005024222 0ustar ischnellusers00000000000000# Update class names from the immediately prior version only # to ensure that cycles are not possible from apptools.persistence.updater import Updater def cleanup_foo(self, state): print 'cleaning up Foo0' state['firstname'] = state['prenom'] state['lastname'] = state['surnom'] del state['prenom'] del state['surnom'] '''for key in state: print '%s state ---> %s' % (key, state[key]) ''' #self.__setstate_original__(state) self.__dict__.update(state) def update_project(self, state): print 'updating to v1' metadata = state['metadata'] metadata['version'] = 1 metadata['diesel'] = 'E300TD' return state class Update1(Updater): def __init__(self): self.refactorings = { ("__main__", "Foo0"): ("__main__", "Foo"), } self.setstates = { ("cplab.project", "Project"): update_project } apptools-4.1.0/LICENSE.txt0000644000175100001440000000312011674464005016164 0ustar ischnellusers00000000000000This software is OSI Certified Open Source Software. OSI Certified is a certification mark of the Open Source Initiative. Copyright (c) 2006, Enthought, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Enthought, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. apptools-4.1.0/README.rst0000644000175100001440000000357611674464005016047 0ustar ischnellusers00000000000000=========================== apptools: application tools =========================== http://github.enthought.com/apptools The apptools project includes a set of packages that Enthought has found useful in creating a number of applications. They implement functionality that is commonly needed by many applications - **apptools.appscripting**: Framework for scripting applications. - **apptools.help**: Provides a plugin for displaying documents and examples and running demos in Envisage Workbench applications. - **apptools.io**: Provides an abstraction for files and folders in a file system. - **apptools.logger**: Convenience functions for creating logging handlers - **apptools.naming**: Manages naming contexts, supporting non-string data types and scoped preferences - **apptools.permissions**: Supports limiting access to parts of an application unless the user is appropriately authorised (not full-blown security). - **apptools.persistence**: Supports pickling the state of a Python object to a dictionary, which can then be flexibly applied in restoring the state of the object. - **apptools.preferences**: Manages application preferences. - **pyface.resource**: Manages application resources such as images and sounds. - **apptools.scripting**: A framework for automatic recording of Python scripts. - **apptools.sweet_pickle**: Handles class-level versioning, to support loading of saved data that exist over several generations of internal class structures. - **apptools.template**: Supports creating templatizable object hierarchies. - **apptools.type_manager**: Manages type extensions, including factories to generate adapters, and hooks for methods and functions. - **apptools.undo**: Supports undoing and scripting application commands. Prerequisites ------------- * `configobj `_ * `traits `_ apptools-4.1.0/setup.py0000644000175100001440000000333111755026000016044 0ustar ischnellusers00000000000000# Copyright (c) 2008-2012 by Enthought, Inc. # All rights reserved. from os.path import join from setuptools import setup, find_packages info = {} execfile(join('apptools', '__init__.py'), info) setup( name = 'apptools', version = info['__version__'], author = 'Enthought, Inc.', author_email = 'info@enthought.com', maintainer = 'ETS Developers', maintainer_email = 'enthought-dev@enthought.com', url = 'https://github.com/enthought/apptools', download_url = ('http://www.enthought.com/repo/ets/apptools-%s.tar.gz' % info['__version__']), classifiers = [c.strip() for c in """\ Development Status :: 5 - Production/Stable Intended Audience :: Developers Intended Audience :: Science/Research License :: OSI Approved :: BSD License Operating System :: MacOS Operating System :: Microsoft :: Windows Operating System :: OS Independent Operating System :: POSIX Operating System :: Unix Programming Language :: Python Topic :: Scientific/Engineering Topic :: Software Development Topic :: Software Development :: Libraries """.splitlines() if len(c.strip()) > 0], description = 'application tools', long_description = open('README.rst').read(), include_package_data = True, package_data = dict(apptools=[ 'help/help_plugin/*.ini', 'logger/plugin/*.ini', 'naming/ui/images/*.png', 'help/help_plugin/action/images/*.png', ]), install_requires = info['__requires__'], license = 'BSD', packages = find_packages(), platforms = ["Windows", "Linux", "Mac OS-X", "Unix", "Solaris"], zip_safe = False, ) apptools-4.1.0/image_LICENSE_Nuvola.txt0000644000175100001440000006362511674464005020672 0ustar ischnellusers00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, 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 this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. 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 not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the 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 specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey 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 library 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 2.1 of the License, or (at your option) any later version. This library 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 library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! apptools-4.1.0/apptools/0000755000175100001440000000000011755026000016173 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/type_manager/0000755000175100001440000000000011674464005020661 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/type_manager/util.py0000644000175100001440000000345711674464005022221 0ustar ischnellusers00000000000000""" Useful functions to do with types! fixme: I don't really like collections of functions, but I'm not sure where these should go? Class methods on 'TypeManager'? """ # Standard library imports. from inspect import getclasstree def sort_by_class_tree(objs): """ Sorts a list of objects by their class tree. Each item in the list should inherit from a common base class. The list is returned ordered from the most specific to the least specific. """ # Since all objects must inherit from a common base class the list # returned by 'getclasstree' will be of length two:- # # The first element is a tuple of the form:- # # (CommonBaseClass, (HasTraits,)) # # The second element is a possibly nested list containing all of the # classes derived from 'CommonBaseClass'. hierarchy = getclasstree([type(obj) for obj in objs]) # Do an in-order traversal of the tree and return just the classes. # # This returns them ordered from least specific to most specific. classes = get_classes(hierarchy) # Now we can actually do the sort! def by_class_tree(x, y): ix = classes.index(type(x)) iy = classes.index(type(y)) # Note the reverse comparison (i.e., compare y with x). This is # because we want to return the classes ordered from the MOST # specfic to the least specific. return cmp(iy, ix) objs.sort(by_class_tree) return def get_classes(hierarchy): """ Walks a class hierarchy and returns all of the classes. """ classes = [] for item in hierarchy: if type(item) is tuple: classes.append(item[0]) else: classes.extend(get_classes(item)) return classes #### EOF ###################################################################### apptools-4.1.0/apptools/type_manager/tests/0000755000175100001440000000000011674464005022023 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/type_manager/tests/type_manager_test_case.py0000644000175100001440000002654511674464005027116 0ustar ischnellusers00000000000000""" Tests the type manager. """ # Standard library imports. import unittest # Enthought library imports. from traits.api import HasTraits, Instance, Str from apptools.type_manager import AdapterFactory, Factory, TypeManager # Test classes. class Foo(HasTraits): name = Str def foogle(self): return 'Foo.foogle.%s' % self.name class SubOfFoo(Foo): def foogle(self): return 'Sub.foogle.%s' % self.name class EmptySubOfFoo(Foo): pass class Bar(HasTraits): name = Str def blargle(self): return 'Bar.blargle.%s' % self.name # Test adapters and factories. class FooToBarAdapter(HasTraits): adaptee = Instance(Foo) def blargle(self): return self.adaptee.foogle() class FooToBarAdapterFactory(AdapterFactory): #### 'AdapterFactory' interface ########################################### # The type of object that the factory can adapt. adaptee_class = Foo # The adapter class (the class that adapts the adaptee to the target # class). adapter_class = FooToBarAdapter # The target class (the class that the factory can adapt objects to). target_class = Bar class SubOfFooToBarAdapter(HasTraits): adaptee = Instance(SubOfFoo) def blargle(self): return self.adaptee.foogle() class SubOfFooToBarAdapterFactory(AdapterFactory): #### 'AdapterFactory' interface ########################################### # The type of object that the factory can adapt. adaptee_class = SubOfFoo # The adapter class (the class that adapts the adaptee to the target # class). adapter_class = SubOfFooToBarAdapter # The target class (the class that the factory can adapt objects to). target_class = Bar class BarFactory(Factory): def can_create(self, target_class, *args, **kw): return target_class is Bar def create(self, target_class, *args, **kw): return Bar(*args, **kw) class TypeManagerTestCase(unittest.TestCase): """ Tests the type manager. """ ########################################################################### # 'TestCase' interface. ########################################################################### def setUp(self): """ Prepares the test fixture before each test method is called. """ # An empty type manager. self.type_manager = TypeManager() return def tearDown(self): """ Called immediately after each test method has been called. """ return ########################################################################### # Tests. ########################################################################### def test_no_adapter_required(self): """ no adapter required """ # Create a Bar. b = Bar() # Try to adapt it to a Bar. bar = self.type_manager.object_as(b, Bar) # The type manager should simply return the same object. self.assert_(bar is b) return def test_no_adapter(self): """ no adapter available """ # Create a Foo. foo = Foo(name='fred') # Try to adapt it to a bar. bar = self.type_manager.object_as(foo, Bar) # There should be no way to adapt a Foo to a Bar. self.assertEqual(bar, None) return def test_instance_adapter(self): """ instance adapter """ # Create a Foo. foo = Foo(name='fred') # Register an adapter Foo->Bar on the INSTANCE (this one should take # precedence). self.type_manager.register_instance_adapters( FooToBarAdapterFactory(), foo ) # Register an adapter Foo->Bar on the TYPE (this will fail if it gets # picked up since it won't actually adapt 'Foo' objects!). self.type_manager.register_instance_adapters( SubOfFooToBarAdapterFactory(), Foo ) # Adapt it to a Bar. bar = self.type_manager.object_as(foo, Bar) self.assertNotEqual(bar, None) self.assertEqual(bar.blargle(), 'Foo.foogle.fred') return def test_unregister_instance_adapter(self): """ unregister instance adapter """ # Create a Foo. foo = Foo(name='fred') # The factory. factory = FooToBarAdapterFactory() # Register an adapter Foo->Bar on the INSTANCE (this one should take # precedence). self.type_manager.register_instance_adapters(factory, foo) # Adapt it to a Bar. bar = self.type_manager.object_as(foo, Bar) self.assertNotEqual(bar, None) self.assertEqual(bar.blargle(), 'Foo.foogle.fred') # Remove the adapter. self.type_manager.unregister_instance_adapters(factory, foo) # Now we shouldn't be able to adapt the object. # # Try to adapt it to a bar. bar = self.type_manager.object_as(foo, Bar) # There should be no way to adapt a Foo to a Bar. self.assertEqual(bar, None) return def test_adapter_on_class(self): """ an adapter registered on an object's actual class. """ # Register an adapter Foo->Bar. self.type_manager.register_type_adapters(FooToBarAdapterFactory(), Foo) # Create a Foo. foo = Foo(name='fred') # Adapt it to a Bar. bar = self.type_manager.object_as(foo, Bar) self.assertNotEqual(bar, None) self.assertEqual(bar.blargle(), 'Foo.foogle.fred') return def test_adapter_on_base_class(self): """ an adapter registered on one of an object's base classes. """ # Register an adapter Foo->Bar. self.type_manager.register_type_adapters(FooToBarAdapterFactory(), Foo) # Create an instance of a class derived from Foo. sub = SubOfFoo(name='fred') # Adapt it to a Bar. bar = self.type_manager.object_as(sub, Bar) self.assertNotEqual(bar, None) self.assertEqual(bar.blargle(), 'Sub.foogle.fred') return def test_ignore_adapter_on_class(self): """ ignore an adapter on an object's actual class. """ # Register an adapter SubOfFoo->Bar on the Foo class. self.type_manager.register_type_adapters( SubOfFooToBarAdapterFactory(), Foo ) # Create a Foo. foo = Foo(name='fred') # Adapt it to a Bar. bar = self.type_manager.object_as(foo, Bar) # There should be no way to adapt a Foo to a Bar. self.assertEqual(bar, None) return def test_ignore_adapter_on_derived_class(self): """ ignore an adapter registered on a derived class. """ # Register an adapter Foo->Bar on the SubOfFoo class. self.type_manager.register_type_adapters( FooToBarAdapterFactory(), SubOfFoo ) # Create a Foo. foo = Foo(name='fred') # Adapt it to a Bar. bar = self.type_manager.object_as(foo, Bar) # There should be no way to adapt a Foo to a Bar. self.assertEqual(bar, None) return def test_unregister_adapter(self): """ unregister an adapter. """ factory = FooToBarAdapterFactory() # Register an adapter Foo->Bar. self.type_manager.register_type_adapters(factory, Foo) # Create a Foo. foo = Foo(name='fred') # Adapt it to a Bar. bar = self.type_manager.object_as(foo, Bar) self.assertNotEqual(bar, None) self.assertEqual(bar.blargle(), 'Foo.foogle.fred') # Unregister the adapter. self.type_manager.unregister_type_adapters(factory) # Try to adapt it to a Bar. bar = self.type_manager.object_as(foo, Bar) # There should be no way to adapt a Foo to a Bar. self.assertEqual(bar, None) return def test_factory(self): """ simple factory """ # Create a Bar factory. factory = BarFactory() # Try to create a Bar using the factory. bar = self.type_manager.object_as(factory, Bar, name='joe') self.assertNotEqual(bar, None) self.assertEqual(bar.blargle(), 'Bar.blargle.joe') return def test_pre_hook(self): """ pre hook """ l = [] def hook(*args, **kw): l.append('Hello') # Create a Foo. foo = Foo(name='fred') # Hook a method. self.type_manager.add_pre(Foo, 'foogle', hook) # Call the method that we have hooked. foo.foogle() # Make sure that the hook was called. self.assertEqual(len(l), 1) # Remove the hook. self.type_manager.remove_pre(Foo, 'foogle', hook) # Call the method that we have hooked. foo.foogle() # Make sure that the hook was NOT called. self.assertEqual(len(l), 1) return def test_post_hook(self): """ post hook """ def hook(result, *args, **kw): return 'Hello' # Create a Foo. foo = Foo(name='fred') # Hook a method. self.type_manager.add_post(Foo, 'foogle', hook) # Call the method that we have hooked. self.assertEqual(foo.foogle(), 'Hello') # Remove the hook. self.type_manager.remove_post(Foo, 'foogle', hook) # Call the method that we have hooked. foo.foogle() # Make sure that the hook was NOT called. self.assertEqual(foo.foogle(), 'Foo.foogle.fred') return def test_pre_hook_on_inherited_method(self): """ test pre hook on an inherited method """ l = [] def hook(*args, **kw): l.append('Hello') # Create an instance of a subclass of Foo that does NOT override # 'foogle'. esof = EmptySubOfFoo(name='fred') # Prove that it does not override 'foogle'! method = EmptySubOfFoo.__dict__.get('foogle') self.assertEqual(method, None) # Hook a method. self.type_manager.add_pre(EmptySubOfFoo, 'foogle', hook) # Make sure that the method was added to the class dictionary. method = EmptySubOfFoo.__dict__.get('foogle') self.assertNotEqual(method, None) # Call the method that we have hooked. esof.foogle() # Make sure that the hook was called. self.assertEqual(len(l), 1) # Remove the hook. self.type_manager.remove_pre(EmptySubOfFoo, 'foogle', hook) # Call the method that we have hooked. esof.foogle() # Make sure that the hook was NOT called. self.assertEqual(len(l), 1) # Make sure that we didn't put the original method back onto # 'EmptySubOfFoo'(since it didn't override it in the first place). method = EmptySubOfFoo.__dict__.get('foogle') self.assertEqual(method, None) return def test_type_manager_hierarchy(self): """ type manager hierarchy """ # Register an adapter Foo->Bar. self.type_manager.register_type_adapters(FooToBarAdapterFactory(), Foo) # Create an empy type manager with the main type manager as its # parent. type_manager = TypeManager(parent=self.type_manager) # Create a Foo. foo = Foo(name='fred') # Adapt it to a Bar. bar = type_manager.object_as(foo, Bar) self.assertNotEqual(bar, None) self.assertEqual(bar.blargle(), 'Foo.foogle.fred') return #### EOF ###################################################################### apptools-4.1.0/apptools/type_manager/api.py0000644000175100001440000000075211674464005022010 0ustar ischnellusers00000000000000from abstract_adapter_factory import AbstractAdapterFactory from abstract_factory import AbstractFactory from abstract_type_system import AbstractTypeSystem from adaptable import Adaptable from adapter import Adapter from adapter_factory import AdapterFactory from adapter_manager import AdapterManager from factory import Factory from hook import add_pre, add_post, remove_pre, remove_post from python_type_system import PythonObject, PythonTypeSystem from type_manager import TypeManager apptools-4.1.0/apptools/type_manager/type_manager.py0000644000175100001440000001545611674464005023721 0ustar ischnellusers00000000000000""" A type manager. """ # Enthought library imports. from traits.api import HasTraits, Instance, Property, Str # Local imports. from abstract_type_system import AbstractTypeSystem from adapter_manager import AdapterManager from factory import Factory from hook import add_pre, add_post, remove_pre, remove_post from python_type_system import PythonTypeSystem class TypeManager(HasTraits): """ A type manager. The type manager allows for objects to be created/adapted to a particular type. """ #### 'TypeManager' interface ############################################## # The adapter manager looks after errr, all adapters. adapter_manager = Property(Instance(AdapterManager)) # The type manager's globally unique identifier (only required if you have # more than one type manager of course!). id = Str # The parent type manager. # # By default this is None, but you can use it to set up a hierarchy of # type managers. If a type manager fails to adapt or create an object of # some target class then it will give its parent a chance to do so. parent = Instance('TypeManager') # The type system used by the manager (it determines 'is_a' relationships # and type MROs etc). # # By default we use standard Python semantics. type_system = Instance(AbstractTypeSystem, PythonTypeSystem()) #### Private interface #################################################### # The adapter manager looks after errr, all adapters. _adapter_manager = Instance(AdapterManager) ########################################################################### # 'TypeManager' interface. ########################################################################### #### Properties ########################################################### def _get_adapter_manager(self): """ Returns the adapter manager. """ return self._adapter_manager #### Methods ############################################################## def object_as(self, obj, target_class, *args, **kw): """ Adapts or creates an object of the target class. Returns None if no appropriate adapter or factory is available. """ # If the object is already an instance of the target class then we # simply return it. if self.type_system.is_a(obj, target_class): result = obj # If the object is a factory that creates instances of the target class # then ask it to produce one. elif self._is_factory_for(obj, target_class, *args, **kw): result = obj.create(target_class, *args, **kw) # Otherwise, see if the object can be adapted to the target class. else: result = self._adapter_manager.adapt(obj, target_class, *args,**kw) # If this type manager couldn't do the job, then give its parent a go! if result is None and self.parent is not None: result = self.parent.object_as(obj, target_class, *args, **kw) return result # Adapters. def register_adapters(self, factory, adaptee_class): """ Registers an adapter factory. 'adaptee_class' can be either a class or the name of a class. """ self._adapter_manager.register_adapters(factory, adaptee_class) return def unregister_adapters(self, factory): """ Unregisters an adapter factory. """ self._adapter_manager.unregister_adapters(factory) return def register_instance_adapters(self, factory, obj): """ Registers an adapter factory for an individual instance. A factory can be in exactly one manager (as it uses the manager's type system). """ self._adapter_manager.register_instance_adapters(factory, obj) return def unregister_instance_adapters(self, factory, obj): """ Unregisters an adapter factory for an individual instance. A factory can be in exactly one manager (as it uses the manager's type system). """ self._adapter_manager.unregister_instance_adapters(factory, obj) return def register_type_adapters(self, factory, adaptee_class): """ Registers an adapter factory. 'adaptee_class' can be either a class or the name of a class. """ self._adapter_manager.register_type_adapters(factory, adaptee_class) return def unregister_type_adapters(self, factory): """ Unregisters an adapter factory. """ self._adapter_manager.unregister_type_adapters(factory) return # Categories. # # Currently, there is no technical reason why we have this convenience # method to add categories. However, it may well turn out to be useful to # track all categories added via the type manager. def add_category(self, klass, category_class): """ Adds a category to a class. """ klass.add_trait_category(category_class) return # Hooks. # # Currently, there is no technical reason why we have these convenience # methods to add and remove hooks. However, it may well turn out to be # useful to track all hooks added via the type manager. def add_pre(self, klass, method_name, callable): """ Adds a pre-hook to method 'method_name' on class 'klass. """ add_pre(klass, method_name, callable) return def add_post(self, klass, method_name, callable): """ Adds a post-hook to method 'method_name' on class 'klass. """ add_post(klass, method_name, callable) return def remove_pre(self, klass, method_name, callable): """ Removes a pre-hook to method 'method_name' on class 'klass. """ remove_pre(klass, method_name, callable) return def remove_post(self, klass, method_name, callable): """ Removes a post-hook to method 'method_name' on class 'klass. """ remove_post(klass, method_name, callable) return ########################################################################### # Private interface. ########################################################################### #### Initializers ######################################################### def __adapter_manager_default(self): """ Initializes the '_adapter_manager' trait. """ return AdapterManager(type_system=self.type_system) #### Methods ############################################################## def _is_factory_for(self, obj, target_class, *args, **kw): """ Returns True iff the object is a factory for the target class. """ is_factory_for = self.type_system.is_a(obj, Factory) \ and obj.can_create(target_class, *args, **kw) return is_factory_for #### EOF ###################################################################### apptools-4.1.0/apptools/type_manager/abstract_type_system.py0000644000175100001440000000167611674464005025515 0ustar ischnellusers00000000000000""" The abstract base class for type systems. """ # Enthought library imports. from traits.api import HasTraits class AbstractTypeSystem(HasTraits): """ The abstract base class for type systems. A type system is responsible for:- 1) Determining whether an object is of a particular type. 2) Determining the MRO of a type. See 'PythonTypeSystem' for an implementation with standard Python semantics. """ ########################################################################### # 'AbstractTypeSystem' interface. ########################################################################### def is_a(self, obj, type): """ Is an object an instance of the specified type? """ raise NotImplementedError def get_mro(self, type): """ Returns the MRO of a type. """ raise NotImplementedError #### EOF ###################################################################### apptools-4.1.0/apptools/type_manager/abstract_adapter_factory.py0000644000175100001440000000550011674464005026265 0ustar ischnellusers00000000000000""" Abstract base class for all adapter factories. """ # Standard library imports. import logging # Enthought library imports. from traits.api import Delegate, HasTraits, Instance # Local imports. from adapter_manager import AdapterManager logger = logging.getLogger(__name__) class AbstractAdapterFactory(HasTraits): """ Abstract base class for all adapter factories. Adapter factories define behavioural extensions for classes. """ #### 'AbstractAdapterFactory' interface ################################### # The adapter manager that the factory is registered with (this will be # None iff the factory is not registered with a manager). adapter_manager = Instance(AdapterManager) # The type system used by the factory (it determines 'is_a' relationships # and type MROs etc). By default we use standard Python semantics. type_system = Delegate('adapter_manager') ########################################################################### # 'AbstractAdapterFactory' interface. ########################################################################### def adapt(self, adaptee, target_class, *args, **kw): """ Returns an adapter that adapts an object to the target class. Returns None if the factory cannot produce such an adapter. """ if self._can_adapt(adaptee, target_class, *args, **kw): adapter = self._adapt(adaptee, target_class, *args, **kw) if adapter is None: logger.warn(self._get_warning_message(adaptee, target_class)) else: adapter = None return adapter ########################################################################### # Protected 'AbstractAdapterFactory' interface. ########################################################################### def _can_adapt(self, adaptee, target_class, *args, **kw): """ Returns True if the factory can produce an appropriate adapter. """ raise NotImplementedError def _adapt(self, adaptee, target_class, *args, **kw): """ Returns an adapter that adapts an object to the target class. """ raise NotImplementedError ########################################################################### # Private interface. ########################################################################### def _get_warning_message(self, adaptee, target_class): """ Returns a warning message. The warning message is used when a factory fails to adapt something that it said it could! """ message = '%s failed to adapt %s to %s' % ( self.__class__.__name__, str(adaptee), target_class.__name__ ) return message #### EOF ###################################################################### apptools-4.1.0/apptools/type_manager/adapter_factory.py0000644000175100001440000000364711674464005024414 0ustar ischnellusers00000000000000""" An adapter factory. """ # Enthought library imports. from traits.api import Any # Local imports. from abstract_adapter_factory import AbstractAdapterFactory class AdapterFactory(AbstractAdapterFactory): """ An adapter factory. This implementation provides the common case where the factory adapts exactly one type of object to exactly one target type using exactly one adapter. This class requires the adapter class to have an 'adaptee' trait. The default 'Adapter' class provides exactly that. """ #### 'AdapterFactory' interface ########################################### # fixme: These trait definitions should be 'Class' but currently this only # allows old-style classes! # # The type of object that the factory can adapt. adaptee_class = Any # The adapter class (the class that adapts the adaptee to the target # class). adapter_class = Any # The target class (the class that the factory can adapt objects to). target_class = Any ########################################################################### # Protected 'AbstractAdapterFactory' interface. ########################################################################### def _can_adapt(self, adaptee, target_class, *args, **kw): """ Returns True if the factory can produce an appropriate adapter. """ can_adapt = target_class is self.target_class \ and self.type_system.is_a(adaptee, self.adaptee_class) return can_adapt def _adapt(self, adaptee, target_class, *args, **kw): """ Returns an adapter that adapts an object to the target class. This requires the adapter class to have an 'adaptee' trait. The default 'Adapter' class provides exactly that. """ return self.adapter_class(adaptee=adaptee, *args, **kw) #### EOF ###################################################################### apptools-4.1.0/apptools/type_manager/python_type_system.py0000644000175100001440000000216511674464005025225 0ustar ischnellusers00000000000000""" A type system with standard(ish) Python semantics. """ # Standard library imports. import inspect # Local imports. from abstract_type_system import AbstractTypeSystem class PythonObject: """ The root type in the type system. fixme: Python is currently a bit broken as it has dual type hierarchies, one for old-style and one for new-style classes. This class is used to create a fake root to unify them. """ __class__ = type class PythonTypeSystem(AbstractTypeSystem): """ A type system with standard(ish) Python semantics. """ ########################################################################### # 'AbstractTypeSystem' interface. ########################################################################### def is_a(self, obj, type): """ Is an object and instance of the specified type? """ return isinstance(obj, type) or type is PythonObject def get_mro(self, type): """ Returns the MRO of a type. """ return list(inspect.getmro(type)) + [PythonObject] #### EOF ###################################################################### apptools-4.1.0/apptools/type_manager/factory.py0000644000175100001440000000230311674464005022700 0ustar ischnellusers00000000000000""" A generic object factory. """ # Enthought library imports. from traits.api import Any # Local imports. from abstract_factory import AbstractFactory class Factory(AbstractFactory): """ A generic object factory. This implementation of the abstract factory interface provides for the common scenario where the factory produces objects of exactly one type. """ #### 'Factory' interface ################################################## # The type of object that we create. # # fixme: This trait definition should be 'Class' but currently this only # allows old-style classes! target_class = Any ########################################################################### # 'AbstractFactory' interface. ########################################################################### def can_create(self, target_class, *args, **kw): """ Returns True if the factory can create objects of a class. """ return target_class is self.target_class def create(self, *args, **kw): """ Creates an object! """ return self.target_class(*args, **kw) #### EOF ###################################################################### apptools-4.1.0/apptools/type_manager/adapter.py0000644000175100001440000000101011674464005022643 0ustar ischnellusers00000000000000""" A default adapter class. """ # Enthought library imports. from traits.api import Any, HasTraits class Adapter(HasTraits): """ A default adapter class. This comes in handy when using the default 'AdapterFactory' as it expects the adpter class to have an 'adaptee' trait. """ #### 'Adapter' interface ################################################## # The object that we are adapting. adaptee = Any #### EOF ###################################################################### apptools-4.1.0/apptools/type_manager/adaptable.py0000644000175100001440000000205211674464005023147 0ustar ischnellusers00000000000000""" The base class for adaptable objects. """ # Enthought library imports. from traits.api import HasTraits, Instance # Local imports. from adapter_manager import AdapterManager class Adaptable(HasTraits): """ The base class for adaptable objects. """ #### 'Adaptable' interface ################################################ # The adapter manager in effect for the object. adapter_manager = Instance(AdapterManager) ########################################################################### # 'Adaptable' interface. ########################################################################### def adapt(self, target_class, *args, **kw): """ Returns True if the factory can produce an appropriate adapter. """ if self.adapter_manager is not None: adapter = self.adapter_manager.adapt( self, target_class, *args, **kw ) else: adapter = None return adapter #### EOF ###################################################################### apptools-4.1.0/apptools/type_manager/hook.py0000644000175100001440000001015311674464005022173 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Implementation of hooked methods. """ def add_pre(klass, method_name, callable): """ Adds a pre-hook to method 'method_name' on class 'klass'. """ _add_hook(klass, method_name, callable, pre=True) return def add_post(klass, method_name, callable): """ Adds a post-hook to method 'method_name' on class 'klass'. """ _add_hook(klass, method_name, callable, pre=False) return def remove_pre(klass, method_name, callable): """ Removes a pre-hook from method 'method_name' on class 'klass'. """ _remove_hook(klass, method_name, callable, pre=True) return def remove_post(klass, method_name, callable): """ Removes a post-hook from method 'method_name' on class 'klass'. """ _remove_hook(klass, method_name, callable, pre=False) return ############################################################################### # Private functions. ############################################################################### def _add_hook(klass, method_name, callable, pre): """ Adds a pre/post hook to method 'method_name' on class 'klass'. """ # Get the method on the klass. method = getattr(klass, method_name) # Have we already hooked it? if hasattr(method, '__pre__'): hooked_method = method # Obviously not! else: def hooked_method(self, *args, **kw): for pre in hooked_method.__pre__: pre(self, *args, **kw) result = method(self, *args, **kw) for post in hooked_method.__post__: result = post(self, result, *args, **kw) return result # Python < 2.4 does not allow this. try: hooked_method.func_name = method_name except: pass hooked_method.__pre__ = [] hooked_method.__post__ = [] # Is the original method actually defined on the class, or is it # inherited? hooked_method.__inherited__ = not klass.__dict__.has_key(method_name) # Save the original method... # # fixme: Twisted uses 'method.im_func' instead of 'method' here, but # both seem to work just as well! setattr(klass, '__hooked__' + method_name, method) # ... and put in the hooked one! setattr(klass, method_name, hooked_method) if pre: hooked_method.__pre__.append(callable) else: hooked_method.__post__.append(callable) return def _remove_hook(klass, method_name, callable, pre): """ Removes a pre/post hook from method 'method_name' on class 'klass'. """ # Get the method on the klass. method = klass.__dict__[method_name] # Is it actually hooked? if hasattr(method, '__pre__'): # Remove the hook. if pre: method.__pre__.remove(callable) else: method.__post__.remove(callable) # If there are no more hooks left then cleanup. if len(method.__pre__) == 0 and len(method.__post__) == 0: # If the method is inherited then just delete the hooked version. if method.__inherited__: delattr(klass, method_name) # Otherwise, reinstate the original method. else: original = getattr(klass, '__hooked__' + method_name) setattr(klass, method_name, original) # Remove the saved original method. delattr(klass, '__hooked__' + method_name) return #### EOF ###################################################################### apptools-4.1.0/apptools/type_manager/__init__.py0000644000175100001440000000064311674464005022775 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2006 by Enthought, Inc. # All rights reserved. #------------------------------------------------------------------------------ """ Manages type extensions, including factories to generate adapters, and hooks for methods and functions. Part of the AppTools project of the Enthought Tool Suite. """ from api import * apptools-4.1.0/apptools/type_manager/abstract_factory.py0000644000175100001440000000412411674464005024566 0ustar ischnellusers00000000000000""" Abstract base class for all object factories. """ # Standard library imports. import logging # Enthought library imports. from traits.api import HasTraits logger = logging.getLogger(__name__) class AbstractFactory(HasTraits): """ Abstract base class for all object factories. """ ########################################################################### # 'AbstractFactory' interface. ########################################################################### def create(self, target_class, *args, **kw): """ Creates an object of the specified target class. Returns None if the factory cannot produce an object of the target class. """ if self._can_create(target_class, *args, **kw): obj = self._create(target_class, *args, **kw) if obj is None: logger.warn(self._get_warning_message(target_class)) else: obj = None return obj ########################################################################### # Protected 'AbstractFactory' interface. ########################################################################### def _can_create(self, target_class, *args, **kw): """ Returns True if the factory can create objects of a class. """ return NotImplementedError def _create(self, target_class, *args, **kw): """ Creates an object of the specified target class. """ raise NotImplementedError ########################################################################### # Private interface. ########################################################################### def _get_warning_message(self, target_class): """ Returns a warning message. The warning message is used when a factory fails to create something that it said it could! """ message = "%s failed to create a %s" % ( self.__class__.__name__, target_class.__name__ ) return message #### EOF ###################################################################### apptools-4.1.0/apptools/type_manager/adapter_manager.py0000644000175100001440000001741311674464005024353 0ustar ischnellusers00000000000000""" A manager for adapter factories. """ # Enthought library imports. from traits.api import Dict, HasTraits, Instance, Property # Local imports. from abstract_type_system import AbstractTypeSystem from python_type_system import PythonTypeSystem class AdapterManager(HasTraits): """ A manager for adapter factories. """ #### 'AdapterManager' interface ########################################### # All registered type-scope factories by the type of object that they # adapt. # # The dictionary is keyed by the *name* of the class rather than the class # itself to allow for adapter factory proxies to register themselves # without having to load and create the factories themselves (i.e., to # allow us to lazily load adapter factories contributed by plugins). This # is a slight compromise as it is obviously geared towards use in Envisage, # but it doesn't affect the API other than allowing a class OR a string to # be passed to 'register_adapters'. # # { String adaptee_class_name : List(AdapterFactory) factories } type_factories = Property(Dict) # All registered instance-scope factories by the object that they adapt. # # { id(obj) : List(AdapterFactory) factories } instance_factories = Property(Dict) # The type system used by the manager (it determines 'is_a' relationships # and type MROs etc). By default we use standard Python semantics. type_system = Instance(AbstractTypeSystem, PythonTypeSystem()) #### Private interface #################################################### # All registered type-scope factories by the type of object that they # adapt. _type_factories = Dict # All registered instance-scope factories by the object that they adapt. _instance_factories = Dict ########################################################################### # 'AdapterManager' interface. ########################################################################### #### Properties ########################################################### def _get_type_factories(self): """ Returns all registered type-scope factories. """ return self._type_factories.copy() def _get_instance_factories(self): """ Returns all registered instance-scope factories. """ return self._instance_factories.copy() #### Methods ############################################################## def adapt(self, adaptee, target_class, *args, **kw): """ Returns an adapter that adapts an object to the target class. 'adaptee' is the object that we want to adapt. 'target_class' is the class that the adaptee should be adapted to. Returns None if no such adapter exists. """ # If the object is already an instance of the target class then we # simply return it. if self.type_system.is_a(adaptee, target_class): adapter = adaptee # Otherwise, look at each class in the adaptee's MRO to see if there # is an adapter factory registered that can adapt the object to the # target class. else: # Look for instance-scope adapters first. adapter = self._adapt_instance(adaptee, target_class, *args, **kw) # If no instance-scope adapter was found then try type-scope # adapters. if adapter is None: for adaptee_class in self.type_system.get_mro(type(adaptee)): adapter = self._adapt_type( adaptee, adaptee_class, target_class, *args, **kw ) if adapter is not None: break return adapter def register_instance_adapters(self, factory, obj): """ Registers an instance-scope adapter factory. A factory can be in exactly one manager (as it uses the manager's type system). """ factories = self._instance_factories.setdefault(id(obj), []) factories.append(factory) # A factory can be in exactly one manager. factory.adapter_manager = self return def unregister_instance_adapters(self, factory, obj): """ Unregisters an instance scope adapter factory. A factory can be in exactly one manager (as it uses the manager's type system). """ factories = self._instance_factories.setdefault(id(obj), []) if factory in factories: factories.remove(factory) # A factory can be in exactly one manager. factory.adapter_manager = None return def register_type_adapters(self, factory, adaptee_class): """ Registers a type-scope adapter factory. 'adaptee_class' can be either a class object or the name of a class. A factory can be in exactly one manager (as it uses the manager's type system). """ if isinstance(adaptee_class, basestring): adaptee_class_name = adaptee_class else: adaptee_class_name = self._get_class_name(adaptee_class) factories = self._type_factories.setdefault(adaptee_class_name, []) factories.append(factory) # A factory can be in exactly one manager. factory.adapter_manager = self return def unregister_type_adapters(self, factory): """ Unregisters a type-scope adapter factory. """ for adaptee_class_name, factories in self._type_factories.items(): if factory in factories: factories.remove(factory) # The factory is no longer deemed to be part of this manager. factory.adapter_manager = None return #### DEPRECATED ########################################################### def register_adapters(self, factory, adaptee_class): """ Registers an adapter factory. 'adaptee_class' can be either a class object or the name of a class. A factory can be in exactly one manager (as it uses the manager's type system). """ print 'DEPRECATED: use "register_type_adapters" instead.' self.register_type_adapters(factory, adaptee_class) return def unregister_adapters(self, factory): """ Unregisters an adapter factory. """ print 'DEPRECATED: use "unregister_type_adapters" instead.' self.unregister_type_adapters(factory) return ########################################################################### # Private interface. ########################################################################### def _adapt_instance(self, adaptee, target_class, *args, **kw): """ Returns an adapter that adaptes an object to the target class. Returns None if no such adapter exists. """ for factory in self._instance_factories.get(id(adaptee), []): adapter = factory.adapt(adaptee, target_class, *args, **kw) if adapter is not None: break else: adapter = None return adapter def _adapt_type(self, adaptee, adaptee_class, target_class, *args, **kw): """ Returns an adapter that adapts an object to the target class. Returns None if no such adapter exists. """ adaptee_class_name = self._get_class_name(adaptee_class) for factory in self._type_factories.get(adaptee_class_name, []): adapter = factory.adapt(adaptee, target_class, *args, **kw) if adapter is not None: break else: adapter = None return adapter def _get_class_name(self, klass): """ Returns the full class name for a class. """ return "%s.%s" % (klass.__module__, klass.__name__) #### EOF ###################################################################### apptools-4.1.0/apptools/permissions/0000755000175100001440000000000011674464005020561 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/permissions/api.py0000644000175100001440000000206511674464005021707 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ from adapter_base import AdapterBase from i_policy_manager import IPolicyManager from i_user import IUser from i_user_manager import IUserManager from package_globals import get_permissions_manager, set_permissions_manager from permission import ManagePolicyPermission, ManageUsersPermission, Permission from permissions_manager import PermissionsManager from secure_proxy import SecureHandler, SecureProxy apptools-4.1.0/apptools/permissions/default/0000755000175100001440000000000011674464005022205 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/permissions/default/persistent.py0000644000175100001440000000711411674464005024762 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Standard library imports. import cPickle as pickle import errno import os # Enthought library imports. from traits.etsconfig.api import ETSConfig class Persistent(object): """This persists a Traits class to a file. It is used by the default permissions policy and user manager to implement basic (ie. insecure) shared data storage.""" def __init__(self, factory, file_name, desc): """Initialise the object. factory is a callable that will create a new instance if there is no existing data. file_name is the name of the file in the ETSConfig.application_home directory (or the directory specified by the ETS_PERMS_DATA_DIR environment variable if set) to persist the data to. desc is a description of the data used in exceptions.""" self._factory = factory self._desc = desc # Get the name of the file to use. data_dir = os.environ.get('ETS_PERMS_DATA_DIR', ETSConfig.application_home) self._fname = os.path.join(data_dir, file_name) self._lock = self._fname + '.lock' try: os.makedirs(data_dir) except OSError: pass def lock(self): """Obtain a lock on the persisted data.""" try: os.mkdir(self._lock) except OSError, e: if e.errno == errno.EEXIST: msg = "The lock on %s is held by another application or user." % self._desc else: msg = "Unable to acquire lock on %s: %s." % (self._desc, e) raise PersistentError(msg) def unlock(self): """Release the lock on the persisted data.""" try: os.rmdir(self._lock) except OSError, e: raise PersistentError("Unable to release lock on %s: %s." % (self._desc, e)) def read(self): """Read and return the persisted data.""" try: f = open(self._fname, 'r') try: try: data = pickle.load(f) except: raise PersistentError("Unable to read %s." % self._desc) finally: f.close() except IOError, e: if e.errno == errno.ENOENT: data = self._factory() else: raise PersistentError("Unable to open %s: %s." % (self._desc, e)) return data def write(self, data): """Write the persisted data.""" try: f = open(self._fname, 'w') try: try: pickle.dump(data, f) except: raise PersistentError("Unable to write %s." % self._desc) finally: f.close() except IOError, e: raise PersistentError("Unable to create %s: %s." % (self._desc, e)) class PersistentError(Exception): """The class used for all persistence related exceptions.""" apptools-4.1.0/apptools/permissions/default/policy_manager.py0000644000175100001440000001260211674464005025551 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from pyface.api import error from pyface.action.api import Action from traits.api import Dict, HasTraits, implements, Instance, List # Local imports. from apptools.permissions.i_policy_manager import IPolicyManager from apptools.permissions.permission import ManagePolicyPermission, Permission from apptools.permissions.secure_proxy import SecureProxy from i_policy_storage import IPolicyStorage, PolicyStorageError from role_assignment import role_assignment from role_definition import role_definition class PolicyManager(HasTraits): """The default policy manager implementation. This policy enforces the use of roles. Permissions are associated with roles rather than directly with users. Users are then associated with one or more roles.""" implements(IPolicyManager) #### 'IPolicyManager' interface ########################################### management_actions = List(Instance(Action)) user_permissions = List(Instance(Permission)) #### 'PolicyManager' interface ############################################ # The dictionary of registered permissions keyed on the permission name. permissions = Dict # The policy data storage. policy_storage = Instance(IPolicyStorage) ########################################################################### # 'IPolicyManager' interface. ########################################################################### def bootstrapping(self): """Return True if we are bootstrapping, ie. no roles have been defined or assigned.""" try: bootstrap = self.policy_storage.is_empty() except PolicyStorageError: # Suppress the error and assume it isn't empty. bootstrap = False return bootstrap def load_policy(self, user): """Load the policy for the given user.""" self.user_permissions = [] # See if the policy is to be unloaded. if user is None: return # Get the user's policy. try: user_name, perm_ids = self.policy_storage.get_policy(user.name) except PolicyStorageError, e: error(None, str(e)) return for id in perm_ids: try: permission = self.permissions[id] except KeyError: # This shouldn't happen if referential integrity is maintained. continue self.user_permissions.append(permission) def register_permission(self, permission): """Register the given permission.""" if self.permissions.has_key(permission.id): other = self.permissions[permission.id] if other.application_defined: if permission.application_defined: raise KeyError, 'permission "%s" has already been defined' % permission.id # Use the description from the policy manager, if there is # one, in preference to the application supplied one. if permission.description: other.description = permission.description elif permission.application_defined: # Again, prefer the policy manager description. if other.description: permission.description = other.description self.permissions[permission.id] = permission else: # This should never happen if the policy manager is working # properly. raise KeyError, 'permission "%s" has already been defined by the same policy manager' % permission.id else: self.permissions[permission.id] = permission ########################################################################### # Trait handlers. ########################################################################### def _management_actions_default(self): """Return the management actions to manage the policy.""" actions = [] perm = ManagePolicyPermission() act = Action(name='&Role Definitions...', on_perform=role_definition) actions.append(SecureProxy(act, permissions=[perm], show=False)) act = Action(name='&Role Assignments...', on_perform=role_assignment) actions.append(SecureProxy(act, permissions=[perm], show=False)) return actions def _policy_storage_default(self): """Return the default storage for the policy data.""" # Defer to an external storage manager if there is one. try: from apptools.permissions.external.policy_storage import PolicyStorage except ImportError: from apptools.permissions.default.policy_storage import PolicyStorage return PolicyStorage() apptools-4.1.0/apptools/permissions/default/policy_storage.py0000644000175100001440000001464711674464005025616 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import HasTraits, Instance, implements # Local imports. from i_policy_storage import IPolicyStorage, PolicyStorageError from persistent import Persistent, PersistentError class PolicyStorage(HasTraits): """This implements a policy database that pickles its data in a local file. """ implements(IPolicyStorage) #### Private interface #################################################### # The persisted database. The database itself is a tuple of the role and # assignment dictionaries. The role dictionary is keyed by the role name, # and has a value that is a tuple of the description and the list of # permission names. The assigment dictionary is keyed by the user name and # has a value that is the list of role names. _db = Instance(Persistent) ########################################################################### # 'IPolicyStorage' interface. ########################################################################### def add_role(self, name, description, perm_ids): """Add a new role.""" self._db.lock() try: roles, assigns = self._db.read() if roles.has_key(name): raise PolicyStorageError("The role \"%s\" already exists." % name) roles[name] = (description, perm_ids) self._db.write((roles, assigns)) finally: self._db.unlock() def all_roles(self): """Return a list of all roles.""" roles, _ = self._readonly_copy() return [(name, description) for name, (description, _) in roles.items()] def delete_role(self, name): """Delete a role.""" self._db.lock() try: roles, assigns = self._db.read() if not roles.has_key(name): raise PolicyStorageError("The role \"%s\" does not exist." % name) del roles[name] # Remove the role from any users who have it. for user, role_names in assigns.items(): try: role_names.remove(name) except ValueError: continue assigns[user] = role_names self._db.write((roles, assigns)) finally: self._db.unlock() def get_assignment(self, user_name): """Return the details of the assignment for the given user name.""" _, assigns = self._readonly_copy() try: role_names = assigns[user_name] except KeyError: return '', [] return user_name, role_names def get_policy(self, user_name): """Return the details of the policy for the given user name.""" roles, assigns = self._readonly_copy() try: role_names = assigns[user_name] except KeyError: return '', [] perm_ids = [] for r in role_names: _, perms = roles[r] perm_ids.extend(perms) return user_name, perm_ids def is_empty(self): """See if the database is empty.""" roles, assigns = self._readonly_copy() # Both have to be non-empty for the whole thing to be non-empty. return (len(roles) == 0 or len(assigns) == 0) def matching_roles(self, name): """Return the full name, description and permissions of all the roles that match the given name.""" roles, _ = self._readonly_copy() # Return any role that starts with the name. roles = [(full_name, description, perm_ids) for full_name, (description, perm_ids) in roles.items() if full_name.startswith(name)] return sorted(roles) def modify_role(self, name, description, perm_ids): """Update the description and permissions for the given role.""" self._db.lock() try: roles, assigns = self._db.read() if not roles.has_key(name): raise PolicyStorageError("The role \"%s\" does not exist." % name) roles[name] = (description, perm_ids) self._db.write((roles, assigns)) finally: self._db.unlock() def set_assignment(self, user_name, role_names): """Save the roles assigned to a user.""" self._db.lock() try: roles, assigns = self._db.read() if len(role_names) == 0: # Delete the user, but don't worry if there is no current # assignment. try: del assigns[user_name] except KeyError: pass else: assigns[user_name] = role_names self._db.write((roles, assigns)) finally: self._db.unlock() ########################################################################### # Trait handlers. ########################################################################### def __db_default(self): """Return the default persisted database.""" return Persistent(self._db_factory, 'ets_perms_policydb', "the policy database") ########################################################################### # Private interface. ########################################################################### def _readonly_copy(self): """Return the policy database (which should not be modified).""" try: self._db.lock() try: data = self._db.read() finally: self._db.unlock() except PersistentError, e: raise PolicyStorageError(str(e)) return data @staticmethod def _db_factory(): """Return an empty policy database.""" return ({}, {}) apptools-4.1.0/apptools/permissions/default/i_user_database.py0000644000175100001440000000554111674464005025676 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import Bool, Instance, Interface # Local imports. from i_user_storage import IUserStorage class IUserDatabase(Interface): """The interface to be implemented by a user database for the default user manager.""" # Set if the implementation supports changing a user's password. can_change_password = Bool # Set if the implementation supports adding users. can_add_user = Bool # Set if the implementation supports modifying users. can_modify_user = Bool # Set if the implementation supports deleting users. can_delete_user = Bool # The user data storage. user_storage = Instance(IUserStorage) def bootstrapping(self): """Return True if the user database is bootstrapping. Typically this is when no users have been defined.""" def authenticate_user(self, user): """Authenticate the given user and return True if successful. user implements IUser.""" def unauthenticate_user(self, user): """Unauthenticate the given user and return True if successful. user implements IUser.""" def change_password(self, user): """Change a user's password in the database. This only needs to be reimplemented if 'can_change_password' is True.""" def add_user(self): """Add a user account to the database. This only needs to be reimplemented if 'can_add_user' is True.""" def modify_user(self): """Modify a user account in the database. This only needs to be reimplemented if 'can_modify_user' is True.""" def delete_user(self): """Delete a user account from the database. This only needs to be reimplemented if 'can_delete_user' is True.""" def matching_user(self, name): """Return an object that implements IUser for the user selected based on the given name. If there was no user selected then return None. How the name is interpreted (eg. as a regular expression) is determined by the user database. Note that the blob attribute of the user will not be set.""" def user_factory(self): """Return a new object that implements the IUser interface.""" apptools-4.1.0/apptools/permissions/default/api.py0000644000175100001440000000166711674464005023342 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ from i_policy_storage import IPolicyStorage, PolicyStorageError from i_user_database import IUserDatabase from i_user_storage import IUserStorage, UserStorageError from policy_manager import PolicyManager from user_database import UserDatabase from user_manager import UserManager apptools-4.1.0/apptools/permissions/default/role_definition.py0000644000175100001440000001466011674464005025737 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from pyface.api import confirm, error, YES from traits.api import Instance from traitsui.api import Group, Handler, Item, SetEditor, View from traitsui.menu import Action, CancelButton # Local imports. from apptools.permissions.package_globals import get_permissions_manager from apptools.permissions.permission import Permission from i_policy_storage import PolicyStorageError from policy_data import Role from select_role import select_role class _RoleView(View): """The view for handling roles.""" #### 'View' interface ##################################################### kind = 'modal' title = "Define roles" ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **traits): """Initialise the object.""" buttons = [Action(name="Search"), Action(name="Add"), Action(name="Modify"), Action(name="Delete"), CancelButton] all_perms = get_permissions_manager().policy_manager.permissions.values() perms_editor = SetEditor(values=all_perms, left_column_title="Available Permissions", right_column_title="Assigned Permissions") perms_group = Group(Item(name='permissions', editor=perms_editor), label='Permissions', show_border=True, show_labels=False) super(_RoleView, self).__init__(Item(name='name'), Item(name='description'), perms_group, buttons=buttons, **traits) class _RoleHandler(Handler): """The view handler for roles.""" ########################################################################### # Trait handlers. ########################################################################### def _search_clicked(self, info): """Invoked by the "Search" button.""" role = self._role(info) # Get all roles that satisfy the criteria. try: roles = get_permissions_manager().policy_manager.policy_storage.matching_roles(role.name) except PolicyStorageError, e: self._ps_error(e) return if len(roles) == 0: self._error("There is no role that matches \"%s\"." % role.name) return name, description, perm_ids = select_role(roles) if name: # Update the viewed object. role.name = name role.description = description role.permissions = self._perms_to_list(perm_ids) def _add_clicked(self, info): """Invoked by the "Add" button.""" role = self._validate(info) if role is None: return # Add the data to the database. try: get_permissions_manager().policy_manager.policy_storage.add_role( role.name, role.description, [p.id for p in role.permissions]) info.ui.dispose() except PolicyStorageError, e: self._ps_error(e) def _modify_clicked(self, info): """Invoked by the "Modify" button.""" role = self._validate(info) if role is None: return # Update the data in the database. try: get_permissions_manager().policy_manager.policy_storage.modify_role( role.name, role.description, [p.id for p in role.permissions]) info.ui.dispose() except PolicyStorageError, e: self._ps_error(e) def _delete_clicked(self, info): """Invoked by the "Delete" button.""" role = self._validate(info) if role is None: return if confirm(None, "Are you sure you want to delete the role \"%s\"?" % role.name) == YES: # Delete the data from the database. try: get_permissions_manager().policy_manager.policy_storage.delete_role(role.name) info.ui.dispose() except PolicyStorageError, e: self._ps_error(e) ########################################################################### # Private interface. ########################################################################### def _validate(self, info): """Validate the role and return it if there were no problems.""" role = self._role(info) role.name = role.name.strip() if not role.name: self._error("A role name must be given.") return None return role def _perms_to_list(self, perm_ids): """Return a list of Permission instances created from the given list of permission ids.""" pl = [] for id in perm_ids: try: p = get_permissions_manager().policy_manager.permissions[id] except KeyError: # FIXME: permissions should be populated from the policy # database - or is it needed at all? Should it just be read # when managing roles? p = Permission(id=id, application_defined=False) pl.append(p) return pl @staticmethod def _role(info): """Return the role instance being handled.""" return info.ui.context['object'] @staticmethod def _error(msg): """Display an error message to the user.""" error(None, msg) @staticmethod def _ps_error(e): """Display a message to the user after a PolicyStorageError exception has been raised.""" error(None, str(e)) def role_definition(): """Implement the role definition for the current policy manager.""" role = Role() view = _RoleView() handler = _RoleHandler() role.edit_traits(view=view, handler=handler) apptools-4.1.0/apptools/permissions/default/user_database.py0000644000175100001440000003664611674464005025400 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Standard library imports. import os # Enthought library imports. from pyface.api import confirm, error, YES from traits.api import Bool, Dict, HasTraits, implements, Instance, \ List, Password, Property, Unicode from traitsui.api import Handler, Item, View from traitsui.menu import Action, OKCancelButtons # Local imports. from apptools.permissions.i_user import IUser from i_user_database import IUserDatabase from i_user_storage import IUserStorage, UserStorageError from select_user import select_user class _LoginUser(HasTraits): """This represents the login data and view.""" #### '_LoginUser' interface ############################################### # The user name. name = Unicode # The user password. password = Password # The default view. traits_view = View(Item(name='name'), Item(name='password'), title="Login", kind='modal', buttons=OKCancelButtons) class _ChangePassword(HasTraits): """This represents the change password data and view.""" #### '_ChangePassword' interface ########################################## # The user name. name = Unicode # The new user password. new_password = Password # The confirmed new user password. confirm_new_password = Password # The default view. traits_view = View(Item(name='name', style='readonly'), Item(name='new_password'), Item(name='confirm_new_password'), title="Change password", kind='modal', buttons=OKCancelButtons) class _ViewUserAccount(HasTraits): """This represents a single account when in a view.""" #### '_ViewUserAccount' interface ######################################### # The name the user uses to identify themselves. name = Unicode # A description of the user (typically their full name). description = Unicode # The password password = Password # The password confirmation. confirm_password = Password # The user database. user_db = Instance(IUserDatabase) class _UserAccountHandler(Handler): """The base traits handler for user account views.""" ########################################################################### # 'Handler' interface. ########################################################################### def close(self, info, is_ok): """Reimplemented to validate the data.""" # If the user cancelled then close the dialog. if not is_ok: return True # Validate the form, only closing the dialog if the form is valid. return self.validate(self._user_account(info)) ########################################################################### # '_UserAccountHandler' interface. ########################################################################### def validate(self, vuac): """Validate the given object and return True if there were no problems. """ if not vuac.name.strip(): self.error("A user name must be given.") return False return True @staticmethod def error(msg): """Display an error message to the user.""" error(None, msg) ########################################################################### # Trait handlers. ########################################################################### def _search_clicked(self, info): """Invoked by the "Search" button.""" # Get the user name. vuac = self._user_account(info) name, description = vuac.user_db._select_user(vuac.name) if not name: return # Update the viewed object. vuac.name = name vuac.description = description vuac.password = vuac.confirm_password = '' ########################################################################### # Private interface. ########################################################################### @staticmethod def _user_account(info): """Return the user account instance being handled.""" return info.ui.context['object'] class _AddUserAccountHandler(_UserAccountHandler): """The traits handler for the add user account view.""" ########################################################################### # '_UserAccountHandler' interface. ########################################################################### def validate(self, vuac): """Validate the given object and return True if there were no problems. """ if not super(_AddUserAccountHandler, self).validate(vuac): return False if not _validate_password(vuac.password, vuac.confirm_password): return False return True class _UserAccountView(View): """The base view for handling user accounts.""" #### 'View' interface ##################################################### kind = 'modal' ########################################################################### # Trait handlers. ########################################################################### def _buttons_default(self): """Return the view's buttons.""" # Create an action that will search the database. buttons = [Action(name="Search")] buttons.extend(OKCancelButtons) return buttons class _AddUserAccountView(_UserAccountView): """A view to handle adding a user account.""" #### 'View' interface ##################################################### title = "Add a user" ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **traits): """Initialise the object.""" super(_AddUserAccountView, self).__init__(Item(name='name'), Item(name='description'), Item(name='password'), Item(name='confirm_password'), **traits) class _ModifyUserAccountView(_AddUserAccountView): """A view to handle modifying a user account.""" #### 'View' interface ##################################################### title = "Modify a user" class _DeleteUserAccountView(_UserAccountView): """A view to handle deleting a user account.""" #### 'View' interface ##################################################### title = "Delete a user" ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **traits): """Initialise the object.""" super(_DeleteUserAccountView, self).__init__(Item(name='name'), Item(name='description', style='readonly'), **traits) class User(HasTraits): """The user implementation. We don't store any extra information other than that defined by IUser.""" implements(IUser) #### 'IUser' interface #################################################### name = Unicode authenticated = Bool(False) description = Unicode blob = Dict class UserDatabase(HasTraits): """This implements a user database that supports IUser for the default user manager (ie. using password authorisation) except that it leaves the actual access of the data to an implementation of IUserStorage.""" implements(IUserDatabase) #### 'IUserDatabase' interface ############################################ can_change_password = Property can_add_user = Property can_modify_user = Property can_delete_user = Property user_storage = Instance(IUserStorage) #### Private interface #################################################### # Set if updating the user blob internally. _updating_blob_internally = Bool(False) ########################################################################### # 'IUserDatabase' interface. ########################################################################### def bootstrapping(self): """See if we are bootstrapping.""" try: bootstrap = self.user_storage.is_empty() except UserStorageError: # Suppress the error and assume it isn't empty. bootstrap = False return bootstrap def authenticate_user(self, user): """Authenticate a user.""" # Get the login details. name = user.name lu = _LoginUser(name=name) if not lu.edit_traits().result: return False # Get the user account and compare passwords. try: name, description, blob = self.user_storage.authenticate_user( lu.name.strip(), lu.password) except UserStorageError, e: self._us_error(e) return False # Update the user details. user.name = name user.description = description # Suppress the trait notification. self._updating_blob_internally = True user.blob = blob self._updating_blob_internally = False return True def unauthenticate_user(self, user): """Unauthenticate a user.""" return self.user_storage.unauthenticate_user(user) def change_password(self, user): """Change a user's password.""" # Get the new password. name = user.name np = _ChangePassword(name=name) if not np.edit_traits().result: return # Validate the password. if not _validate_password(np.new_password, np.confirm_new_password): return # Update the password in the database. try: self.user_storage.update_password(name, np.new_password) except UserStorageError, e: self._us_error(e) def add_user(self): """Add a user.""" # Get the data from the user. vuac = _ViewUserAccount(user_db=self) view = _AddUserAccountView() handler = _AddUserAccountHandler() if vuac.edit_traits(view=view, handler=handler).result: # Add the data to the database. try: self.user_storage.add_user(vuac.name.strip(), vuac.description, vuac.password) except UserStorageError, e: self._us_error(e) def modify_user(self): """Modify a user.""" # Get the data from the user. vuac = _ViewUserAccount(user_db=self) view = _ModifyUserAccountView() handler = _UserAccountHandler() if vuac.edit_traits(view=view, handler=handler).result: # Update the data in the database. try: self.user_storage.modify_user(vuac.name.strip(), vuac.description, vuac.password) except UserStorageError, e: self._us_error(e) def delete_user(self): """Delete a user.""" # Get the data from the user. vuac = _ViewUserAccount(user_db=self) view = _DeleteUserAccountView() handler = _UserAccountHandler() if vuac.edit_traits(view=view, handler=handler).result: # Make absolutely sure. name = vuac.name.strip() if confirm(None, "Are you sure you want to delete the user \"%s\"?" % name) == YES: # Delete the data from the database. try: self.user_storage.delete_user(name) except UserStorageError, e: self._us_error(e) def matching_user(self, name): """Select a user.""" name, description = self._select_user(name) if not name: return None return User(name=name, description=description) def user_factory(self): """Create a new user object.""" user = User(name=os.environ.get('USER', '')) # Monitor when the blob changes. user.on_trait_change(self._blob_changed, name='blob') return user ########################################################################### # Trait handlers. ########################################################################### def _get_can_change_password(self): """See if a user can change their password.""" return 'user_password' in self.user_storage.capabilities def _get_can_add_user(self): """See if a user can be added.""" return 'user_add' in self.user_storage.capabilities def _get_can_modify_user(self): """See if a user can be modified.""" return 'user_modify' in self.user_storage.capabilities def _get_can_delete_user(self): """See if a user can be deleted.""" return 'user_delete' in self.user_storage.capabilities def _user_storage_default(self): """Return the default storage for the user data.""" # Defer to an external storage manager if there is one. try: from apptools.permissions.external.user_storage import UserStorage except ImportError: from apptools.permissions.default.user_storage import UserStorage return UserStorage() def _blob_changed(self, user, tname, old, new): """Invoked when the user's blob data changes.""" if not self._updating_blob_internally: try: self.user_storage.update_blob(user.name, user.blob) except UserStorageError, e: self._us_error(e) ########################################################################### # Private interface. ########################################################################### def _select_user(self, name): """Select a user returning the data as a tuple of name and description. """ # Get all users that satisfy the criteria. try: users = self.user_storage.matching_users(name) except UserStorageError, e: self._us_error(e) return '', '' if len(users) == 0: error(None, "There is no user that matches \"%s\"." % name) return '', '' return select_user(users) @staticmethod def _us_error(e): """Display a message to the user after a UserStorageError exception has been raised. If the message is empty then we assume the user has already been informed.""" msg = str(e) if msg: error(None, msg) def _validate_password(password, confirmation): """Validate a password and return True if it is valid.""" MIN_PASSWORD_LEN = 6 if password != confirmation: error(None, "The passwords do not match.") return False if not password: error(None, "A password must be given.") return False if len(password) < MIN_PASSWORD_LEN: error(None, "The password must be at least %d characters long." % MIN_PASSWORD_LEN) return False return True apptools-4.1.0/apptools/permissions/default/role_assignment.py0000644000175100001440000001307711674464005025760 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from pyface.api import error from traits.api import Dict, Instance from traitsui.api import Group, Handler, Item, SetEditor, View from traitsui.menu import Action, CancelButton # Local imports. from apptools.permissions.package_globals import get_permissions_manager from i_policy_storage import PolicyStorageError from policy_data import Assignment, Role class _AssignmentView(View): """The view for handling role assignments.""" #### 'View' interface ##################################################### kind = 'modal' title = "Assign roles" ########################################################################### # 'object' interface. ########################################################################### def __init__(self, all_roles, **traits): """Initialise the object.""" buttons = [Action(name="Search"), Action(name="Save"), CancelButton] roles_editor = SetEditor(values=all_roles.values(), left_column_title="Available Roles", right_column_title="Assigned Roles") roles_group = Group(Item(name='roles', editor=roles_editor), label='Roles', show_border=True, show_labels=False) super(_AssignmentView, self).__init__(Item(name='user_name'), Item(name='description', style='readonly'), roles_group, buttons=buttons, **traits) class _AssignmentHandler(Handler): """The view handler for role assignments.""" #### Private interface #################################################### all_roles = Dict ########################################################################### # Trait handlers. ########################################################################### def _search_clicked(self, info): """Invoked by the "Search" button.""" pm = get_permissions_manager() assignment = self._assignment(info) user = pm.user_manager.matching_user(assignment.user_name) if user is None: return try: user_name, role_names = pm.policy_manager.policy_storage.get_assignment(user.name) except PolicyStorageError, e: self._ps_error(e) return # Update the viewed object. assignment.user_name = user.name assignment.description = user.description assignment.roles = self._roles_to_list(role_names) def _save_clicked(self, info): """Invoked by the "Save" button.""" assignment = self._validate(info) if assignment is None: return # Update the data in the database. try: get_permissions_manager().policy_manager.policy_storage.set_assignment(assignment.user_name, [r.name for r in assignment.roles]) info.ui.dispose() except PolicyStorageError, e: self._ps_error(e) ########################################################################### # Private interface. ########################################################################### def _validate(self, info): """Validate the assignment and return it if there were no problems.""" assignment = self._assignment(info) assignment.user_name = assignment.user_name.strip() if not assignment.user_name: self._error("A user name must be given.") return None return assignment def _roles_to_list(self, role_names): """Return a list of Role instances created from the given list of role names.""" rl = [] for name in role_names: try: r = self.all_roles[name] except KeyError: # This shouldn't happen if the policy database is maintaining # referential integrity. continue rl.append(r) return rl @staticmethod def _assignment(info): """Return the assignment instance being handled.""" return info.ui.context['object'] @staticmethod def _error(msg): """Display an error message to the user.""" error(None, msg) @staticmethod def _ps_error(e): """Display a message to the user after a PolicyStorageError exception has been raised.""" error(None, str(e)) def role_assignment(): """Implement the role assignment for the current policy manager.""" # Create a dictionary of roles keyed by the role name. all_roles = {} try: roles = get_permissions_manager().policy_manager.policy_storage.all_roles() except PolicyStorageError, e: error(None, str(e)) return for name, description in roles: all_roles[name] = Role(name=name, description=description) assignment = Assignment() view = _AssignmentView(all_roles) handler = _AssignmentHandler(all_roles=all_roles) assignment.edit_traits(view=view, handler=handler) apptools-4.1.0/apptools/permissions/default/policy_data.py0000644000175100001440000000274211674464005025054 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import HasTraits, Instance, List, Unicode # Local imports. from apptools.permissions.permission import Permission class Role(HasTraits): """This represents a role.""" # The role name. name = Unicode # The role description. description = Unicode # The permissions that define the role. permissions = List(Instance(Permission)) def __str__(self): """Return a user friendly representation.""" s = self.description if not s: s = self.name return s class Assignment(HasTraits): """This represents the assignment of roles to a user.""" # The user name. user_name = Unicode # The user description. description = Unicode # The list of assigned roles. roles = List(Instance(Role)) apptools-4.1.0/apptools/permissions/default/i_user_storage.py0000644000175100001440000000633611674464005025601 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import Interface, List, Str class UserStorageError(Exception): """This is the exception raised by an IUserStorage object when an error occurs accessing the database. Its string representation is displayed as an error message to the user.""" class IUserStorage(Interface): """This defines the interface expected by a UserManager instance to handle the low level storage of the user data.""" #### 'IUserStorage' interface ############################################# # A list of strings describing the storage capabilities. 'user_password' # means a user's password can be changed. 'user_add' means a user can be # added. 'user_modify' means a user can be modified. 'user_delete' means # a user can be deleted. capabilities = List(Str) ########################################################################### # 'IUserStorage' interface. ########################################################################### def add_user(self, name, description, password): """Add a new user with the given name, description and password.""" def authenticate_user(self, name, password): """Return a tuple of the name, description, and blob of the user with the given name if they are successfully authenticated with the given password. A tuple of empty strings will be returned if the authentication failed.""" def delete_user(self, name): """Delete the user with the given name (which will not be empty).""" def is_empty(self): """Return True if the user database is empty. It will only ever be called once.""" def matching_users(self, name): """Return a list of tuples of the full name and description of all users, sorted by the full name, that match the given name. How the name is interpreted (eg. as a regular expression) is determined by the storage.""" def modify_user(self, name, description, password): """Update the description and password for the user with the given name (which will not be empty).""" def unauthenticate_user(self, user): """Unauthenticate the given user (which is an object implementing IUser) and return True if it was successful.""" def update_blob(self, name, blob): """Update the blob for the user with the given name (which will not be empty).""" def update_password(self, name, password): """Update the password for the user with the given name (which will not be empty).""" apptools-4.1.0/apptools/permissions/default/select_user.py0000644000175100001440000000442111674464005025075 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import HasTraits, Instance, List, Unicode from traitsui.api import Item, TableEditor, View from traitsui.menu import OKCancelButtons from traitsui.table_column import ObjectColumn class _User(HasTraits): """This represents the user model.""" #### '_User' interface #################################################### # The user name. name = Unicode # The user description. description = Unicode class _UsersView(HasTraits): """This represents the view used to select a user.""" #### '_UsersView' interface ############################################### # The list of users to select from. model = List(_User) # The selected user. selection = Instance(_User) # The editor used by the view. table_editor = TableEditor(columns=[ObjectColumn(name='name'), ObjectColumn(name='description')], selected='selection', sort_model=True, configurable=False) # The default view. traits_view = View(Item('model', show_label=False, editor=table_editor), title="Select a User", style='readonly', kind='modal', buttons=OKCancelButtons) def select_user(users): """Return a single user from the given list of users.""" # Construct the model. model = [_User(name=name, description=description) for name, description in users] # Construct the view. view = _UsersView(model=model) if view.configure_traits() and view.selection is not None: user = view.selection.name, view.selection.description else: user = '', '' return user apptools-4.1.0/apptools/permissions/default/user_manager.py0000644000175100001440000001401411674464005025227 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from pyface.action.api import Action from traits.api import Bool, Event, HasTraits, implements, \ Instance, List, Unicode # Local imports. from apptools.permissions.i_user import IUser from apptools.permissions.i_user_manager import IUserManager from apptools.permissions.package_globals import get_permissions_manager from apptools.permissions.permission import ManageUsersPermission from i_user_database import IUserDatabase class UserManager(HasTraits): """The default user manager implementation.""" implements(IUserManager) #### 'IUserManager' interface ############################################# management_actions = List(Instance(Action)) user = Instance(IUser) user_actions = List(Instance(Action)) user_authenticated = Event(IUser) #### 'UserManager' interface ############################################## # The user database. user_db = Instance(IUserDatabase) ########################################################################### # 'IUserManager' interface. ########################################################################### def bootstrapping(self): """Return True if we are bootstrapping, ie. no users have been defined. """ return self.user_db.bootstrapping() def authenticate_user(self): """Authenticate the user.""" if self.user_db.authenticate_user(self.user): self.user.authenticated = True # Tell the policy manager before everybody else. get_permissions_manager().policy_manager.load_policy(self.user) self.user_authenticated = self.user def unauthenticate_user(self): """Unauthenticate the user.""" if self.user.authenticated and self.user_db.unauthenticate_user(self.user): self.user.authenticated = False # Tell the policy manager before everybody else. get_permissions_manager().policy_manager.load_policy(None) self.user_authenticated = None def matching_user(self, name): """Select a user.""" return self.user_db.matching_user(name) ########################################################################### # Trait handlers. ########################################################################### def _management_actions_default(self): """Return the list of management actions.""" from apptools.permissions.secure_proxy import SecureProxy user_db = self.user_db actions = [] perm = ManageUsersPermission() if user_db.can_add_user: act = Action(name="&Add a User...", on_perform=user_db.add_user) actions.append(SecureProxy(act, permissions=[perm], show=False)) if user_db.can_modify_user: act = Action(name="&Modify a User...", on_perform=user_db.modify_user) actions.append(SecureProxy(act, permissions=[perm], show=False)) if user_db.can_delete_user: act = Action(name="&Delete a User...", on_perform=user_db.delete_user) actions.append(SecureProxy(act, permissions=[perm], show=False)) return actions def _user_actions_default(self): """Return the list of user actions.""" actions = [] if self.user_db.can_change_password: actions.append(_ChangePasswordAction()) return actions def _user_default(self): """Return the default current user.""" return self.user_db.user_factory() def _user_db_default(self): """Return the default user database.""" # Defer to an external user database if there is one. try: from apptools.permissions.external.user_database import UserDatabase except ImportError: from apptools.permissions.default.user_database import UserDatabase return UserDatabase() class _ChangePasswordAction(Action): """An action that allows the current user to change their password. It isn't exported through actions/api.py because it is specific to this user manager implementation.""" #### 'Action' interface ################################################### enabled = Bool(False) name = Unicode("&Change Password...") ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **traits): """Initialise the object.""" super(_ChangePasswordAction, self).__init__(**traits) get_permissions_manager().user_manager.on_trait_event(self._refresh_enabled, 'user_authenticated') ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """Perform the action.""" um = get_permissions_manager().user_manager um.user_db.change_password(um.user) ########################################################################### # Private interface. ########################################################################### def _refresh_enabled(self, user): """Invoked whenever the current user's authorisation state changes.""" self.enabled = user is not None apptools-4.1.0/apptools/permissions/default/__init__.py0000644000175100001440000000010411674464005024311 0ustar ischnellusers00000000000000# Copyright (c) 2008-2011 by Enthought, Inc. # All rights reserved. apptools-4.1.0/apptools/permissions/default/user_storage.py0000644000175100001440000001356311674464005025271 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import HasTraits, Instance, implements # Local imports. from i_user_storage import IUserStorage, UserStorageError from persistent import Persistent, PersistentError class UserStorage(HasTraits): """This implements a user database that pickles its data in a local file. """ implements(IUserStorage) #### 'IUserStorage' interface ############################################# capabilities = ['user_password', 'user_add', 'user_modify', 'user_delete'] #### Private interface #################################################### # The persisted database. The database itself is a dictionary, keyed by # the user name, and with a value that is a tuple of the description, blob # and clear text password. _db = Instance(Persistent) ########################################################################### # 'IUserStorage' interface. ########################################################################### def add_user(self, name, description, password): """Add a new user.""" self._db.lock() try: users = self._db.read() if users.has_key(name): raise UserStorageError("The user \"%s\" already exists." % name) users[name] = (description, {}, password) self._db.write(users) finally: self._db.unlock() def authenticate_user(self, name, password): """Return the tuple of the user name, description, and blob if the user was successfully authenticated.""" users = self._readonly_copy() try: description, blob, pword = users[name] except KeyError: raise UserStorageError("The name or password is invalid.") if password != pword: raise UserStorageError("The name or password is invalid.") return name, description, blob def delete_user(self, name): """Delete a user.""" self._db.lock() try: users = self._db.read() try: del users[name] except KeyError: raise UserStorageError("The user \"%s\" does not exist." % name) self._db.write(users) finally: self._db.unlock() def is_empty(self): """See if the database is empty.""" return (len(self._readonly_copy()) == 0) def matching_users(self, name): """Return the full name and description of all the users that match the given name.""" # Return any user that starts with the name. users = [(full_name, description) for full_name, (description, _, _) in self._readonly_copy().items() if full_name.startswith(name)] return sorted(users) def modify_user(self, name, description, password): """Update the description and password for the given user.""" self._db.lock() try: users = self._db.read() try: _, blob, _ = users[name] except KeyError: raise UserStorageError("The user \"%s\" does not exist." % name) users[name] = (description, blob, password) self._db.write(users) finally: self._db.unlock() def update_blob(self, name, blob): """Update the blob for the given user.""" self._db.lock() try: users = self._db.read() try: description, _, password = users[name] except KeyError: raise UserStorageError("The user \"%s\" does not exist." % name) users[name] = (description, blob, password) self._db.write(users) finally: self._db.unlock() def unauthenticate_user(self, user): """Unauthenticate the given user.""" # There is nothing to do. return True def update_password(self, name, password): """Update the password for the given user.""" self._db.lock() try: users = self._db.read() try: description, blob, _ = users[name] except KeyError: raise UserStorageError("The user \"%s\" does not exist." % name) users[name] = (description, blob, password) self._db.write(users) finally: self._db.unlock() ########################################################################### # Trait handlers. ########################################################################### def __db_default(self): """Return the default persisted database.""" return Persistent(dict, 'ets_perms_userdb', "the user database") ########################################################################### # Private interface. ########################################################################### def _readonly_copy(self): """Return the user database (which should not be modified).""" try: self._db.lock() try: data = self._db.read() finally: self._db.unlock() except PersistentError, e: raise UserStorageError(str(e)) return data apptools-4.1.0/apptools/permissions/default/i_policy_storage.py0000644000175100001440000000561111674464005026115 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import Interface class PolicyStorageError(Exception): """This is the exception raised by an IPolicyStorage object when an error occurs accessing the database. Its string representation is displayed as an error message to the user.""" class IPolicyStorage(Interface): """This defines the interface expected by a PolicyManager to handle the low level storage of the policy data.""" ########################################################################### # 'IPolicyStorage' interface. ########################################################################### def add_role(self, name, description, perm_ids): """Add a new role.""" def all_roles(self): """Return a list of all roles where each element is a tuple of the name and description.""" def delete_role(self, name): """Delete the role with the given name (which will not be empty).""" def get_assignment(self, user_name): """Return a tuple of the user name and list of role names of the assignment for the given user name. The tuple will contain an empty string and list if the user isn't known.""" def get_policy(self, user_name): """Return a tuple of the user name and list of permission names for the user with the given name. The tuple will contain an empty string and list if the user isn't known.""" def is_empty(self): """Return True if the user database is empty. It will only ever be called once.""" def matching_roles(self, name): """Return a list of tuples of the full name, description and list of permission names, sorted by the full name, of all roles that match the given name. How the name is interpreted (eg. as a regular expression) is determined by the storage.""" def modify_role(self, name, description, perm_ids): """Update the description and permissions for the role with the given name (which will not be empty).""" def set_assignment(self, user_name, role_names): """Save the assignment of the given role names to the given user. Note that there may or may not be an existing assignment for the user.""" apptools-4.1.0/apptools/permissions/default/select_role.py0000644000175100001440000000461211674464005025062 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import HasTraits, Instance, List, Unicode from traitsui.api import Item, TableEditor, View from traitsui.menu import OKCancelButtons from traitsui.table_column import ObjectColumn class _Role(HasTraits): """This represents the role model.""" #### '_RoleModel' interface ############################################### # The role name. name = Unicode # The role description. description = Unicode # The permissions ids. permissions = List class _RolesView(HasTraits): """This represents the view used to select a role.""" #### '_UsersView' interface ############################################### # The list of roles to select from. model = List(_Role) # The selected user. selection = Instance(_Role) # The editor used by the view. table_editor = TableEditor(columns=[ObjectColumn(name='name'), ObjectColumn(name='description')], selected='selection', sort_model=True, configurable=False) # The default view. traits_view = View(Item('model', show_label=False, editor=table_editor), title="Select a Role", style='readonly', kind='modal', buttons=OKCancelButtons) def select_role(roles): """Return a single role from the given list of roles.""" # Construct the model. model = [_Role(name=name, description=description, permissions=permissions) for name, description, permissions in roles] # Construct the view. view = _RolesView(model=model) if view.configure_traits() and view.selection is not None: role = view.selection.name, view.selection.description, view.selection.permissions else: role = '', '', [] return role apptools-4.1.0/apptools/permissions/i_user.py0000644000175100001440000000340011674464005022416 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import Bool, Dict, Interface, Unicode class IUser(Interface): """The interface implemented by a user (or principal).""" # The user's name, ie. how they identified themselves to the permissions # policy. It is only valid if the authenticated trait is True. name = Unicode # This is set if the user has been authenticated, ie. the name trait is # valid. authenticated = Bool(False) # An optional description of the user (eg. their full name). The exact # meaning is defined by the user manager. description = Unicode # This allows application defined, user specific data to be persisted in # the user database. An application (or plugin) should save the data as a # single value in the dictionary keyed on the application's (or plugin's) # unique id. The data will be saved in the user database whenever it is # changed, and will be read from the user database whenever the user is # authenticated. blob = Dict def __str__(self): """Return a user friendly representation of the user.""" apptools-4.1.0/apptools/permissions/i_user_manager.py0000644000175100001440000000436311674464005024121 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from pyface.action.api import Action from traits.api import Bool, Event, Instance, Interface, List # Local imports. from i_user import IUser class IUserManager(Interface): """The interface implemented by a user manager to manage users.""" # The list of PyFace management actions (ie. actions related to all users) # implemented by this user manager. management_actions = List(Instance(Action)) # The current user. user = Instance(IUser) # The list of PyFace user actions (ie. actions related to the current user) # implemented by this user manager. user_actions = List(Instance(Action)) # This is fired whenever the currently authenticated user changes. It will # be None if the current user isn't authenticated. user_authenticated = Event(IUser) def bootstrapping(self): """Return True if the user manager is bootstrapping. Typically this is when no users have been defined.""" def authenticate_user(self): """Authenticate (ie. login) the user. If successfully authenticated all secured objects are re-enabled according to the user's permissions. """ def unauthenticate_user(self): """Unauthenticate (ie. logout) the user. All secured objects are disabled.""" def select_user(self, name): """Return an object that implements IUser for the user selected based on the given name. If there was no user selected then return None. How the name is interpreted (eg. as a regular expression) is determined by the user manager.""" apptools-4.1.0/apptools/permissions/permission.py0000644000175100001440000000723011674464005023325 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import Bool, HasTraits, Property, Str, Unicode # Locals imports. from package_globals import get_permissions_manager class Permission(HasTraits): """A permission is the link between an application action and the current user - if the user has a permission attached to the action then the user is allowed to perform that action.""" #### 'Permission' interface ############################################### # The id of the permission. By convention a dotted format is used for the # id with the id of the application being the first part. id = Str # A user friendly description of the permission. description = Unicode # Set if the current user has this permission. This is typically used with # the enabled_when and visible_when traits of a TraitsUI Item object when # the permission instance has been placed in the TraitsUI context. granted = Property # Set if the permission should be granted automatically when bootstrapping. # This is normally only ever set for permissions related to user management # and permissions. The user manager determines exactly what is meant by # "bootstrapping" but it is usually when it determines that no user or # permissions information has been defined. bootstrap = Bool(False) # Set if the permission has been defined by application code rather than as # a result of loading the policy database. application_defined = Bool(True) ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **traits): """Initialise the object.""" super(Permission, self).__init__(**traits) # Register the permission. get_permissions_manager().policy_manager.register_permission(self) def __str__(self): """Return a user friendly representation.""" s = self.description if not s: s = self.id return s ########################################################################### # Trait handlers. ########################################################################### def _get_granted(self): """Check the user has this permission.""" return get_permissions_manager().check_permissions(self) class ManagePolicyPermission(Permission): """The standard permission for managing permissions policies.""" #### 'Permission' interface ############################################### id = Str('ets.permissions.manage_policy') description = Unicode(u"Manage permissions policy") bootstrap = Bool(True) class ManageUsersPermission(Permission): """The standard permission for managing permissions users.""" #### 'Permission' interface ############################################### id = Str('ets.permissions.manage_users') description = Unicode(u"Manage users") bootstrap = Bool(True) apptools-4.1.0/apptools/permissions/permissions_manager.py0000644000175100001440000000760711674464005025212 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import Bool, HasTraits, Instance # Local imports. from i_policy_manager import IPolicyManager from i_user_manager import IUserManager class PermissionsManager(HasTraits): """A singleton class that provides access to the current policy and user managers.""" #### 'PermissionsManager' interface ####################################### # Set if bootstrap permissions should be automatically enabled in a # bootstrap situation (ie. when no policy or user data has been defined). # Bootstrap permissions are normally attached to actions used to define # policy and user data. Normally this is True, unless policy and user data # is to be managed by an external application. allow_bootstrap_permissions = Bool(True) # The current policy manager. policy_manager = Instance(IPolicyManager) # The current user manager. user_manager = Instance(IUserManager) #### Private interface #################################################### # Set if we are bootstrapping. _bootstrap = Bool ########################################################################### # 'PermissionsManager' interface. ########################################################################### def check_permissions(self, *permissions): """Check that the current user has one or more of the given permissions and return True if they have. permissions is a list of Permission instances.""" # Get the current user and their assigned permissions. user = self.user_manager.user user_perms = self.policy_manager.user_permissions for perm in permissions: # If this is a bootstrap permission then see if we are in a # bootstrap situation. if perm.bootstrap and self._bootstrap: return True if user.authenticated and perm in user_perms: return True return False ########################################################################### # Trait handlers. ########################################################################### def _policy_manager_default(self): """Provide a default policy manager.""" # Defer to an external manager if there is one. try: from apptools.permissions.external.policy_manager import PolicyManager except ImportError: from apptools.permissions.default.policy_manager import PolicyManager return PolicyManager() def _user_manager_default(self): """Provide a default user manager.""" # Defer to an external manager if there is one. try: from apptools.permissions.external.user_manager import UserManager except ImportError: from apptools.permissions.default.user_manager import UserManager return UserManager() def __bootstrap_default(self): """Determine whether or not we are bootstrapping. We only want to do it once as checking may involve one or more remote database queries.""" return (self.allow_bootstrap_permissions and (self.policy_manager.bootstrapping() or self.user_manager.bootstrapping())) apptools-4.1.0/apptools/permissions/action/0000755000175100001440000000000011674464005022036 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/permissions/action/user_menu_manager.py0000644000175100001440000000400211674464005026100 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from pyface.action.api import Group, MenuManager from traits.api import Unicode # Local imports. from apptools.permissions.package_globals import get_permissions_manager from login_action import LoginAction from logout_action import LogoutAction class UserMenuManager(MenuManager): """A menu that contains all the actions related to users and permissions. """ #### 'MenuManager' interface ############################################## id = 'User' name = Unicode("&User") ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **traits): """Initialise the object.""" pm = get_permissions_manager() # Put them in a group so we can optionally append (because the PyFace # API doesn't do what you expect with append()). group = Group() group.append(LoginAction()) for act in pm.user_manager.user_actions: group.append(act) group.append(LogoutAction()) for act in pm.user_manager.management_actions: group.append(act) for act in pm.policy_manager.management_actions: group.append(act) super(UserMenuManager, self).__init__(group, **traits) apptools-4.1.0/apptools/permissions/action/api.py0000644000175100001440000000143011674464005023157 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ from login_action import LoginAction from logout_action import LogoutAction from user_menu_manager import UserMenuManager apptools-4.1.0/apptools/permissions/action/login_action.py0000644000175100001440000000254311674464005025061 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from pyface.action.api import Action from traits.api import Unicode # Local imports. from apptools.permissions.package_globals import get_permissions_manager class LoginAction(Action): """An action that authenticates the current user.""" #### 'Action' interface ################################################### name = Unicode("Log&in...") ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """Perform the action.""" get_permissions_manager().user_manager.authenticate_user() apptools-4.1.0/apptools/permissions/action/__init__.py0000644000175100001440000000010411674464005024142 0ustar ischnellusers00000000000000# Copyright (c) 2008-2011 by Enthought, Inc. # All rights reserved. apptools-4.1.0/apptools/permissions/action/logout_action.py0000644000175100001440000000420511674464005025257 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from pyface.action.api import Action from traits.api import Bool, Unicode # Local imports. from apptools.permissions.package_globals import get_permissions_manager class LogoutAction(Action): """An action that unauthenticates the current user.""" #### 'Action' interface ################################################### enabled = Bool(False) name = Unicode("Log&out") ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **traits): """Initialise the object.""" super(LogoutAction, self).__init__(**traits) get_permissions_manager().user_manager.on_trait_event(self._refresh_enabled, 'user_authenticated') ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """Perform the action.""" get_permissions_manager().user_manager.unauthenticate_user() ########################################################################### # Private interface. ########################################################################### def _refresh_enabled(self, user): """Invoked whenever the current user's authorisation state changes.""" self.enabled = user is not None apptools-4.1.0/apptools/permissions/adapter_base.py0000644000175100001440000001155311674464005023552 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import Any, Bool, HasTraits, Instance, List # Local imports. from package_globals import get_permissions_manager from permission import Permission class AdapterBase(HasTraits): """This is the base class for object specific adapters.""" # The object being proxied. proxied = Any # The list of permissions applied to the proxied object. permissions = List(Instance(Permission)) # Set if the proxied object should be shown when it is enabled. show = Bool # The desired enabled state of the proxied object, ie. the state set by the # application before any permissions were applied. _desired_enabled = Bool # The desired visible state of the proxied object, ie. the state set by the # application before any permissions were applied. _desired_visible = Bool # The registered adapter types. _adapter_types = {} @classmethod def register_adapter(cls, adapter, *types): """Register an adapter type for one or more object types.""" for t in types: cls._adapter_types[t] = adapter @classmethod def factory(cls, proxied, permissions, show): """Return an adapter for the proxied object. permissions is a list of permissions to attach to the object. show is set if the proxied object should be visible when it is disabled. """ # Find a suitable adapter type. for object_type, adapter_type in cls._adapter_types.items(): if isinstance(proxied, object_type): break else: raise TypeError, "no SecureProxy adapter registered for %s" % proxied adapter = adapter_type(proxied=proxied, permissions=permissions, show=show) # Refresh the state of the object when the authentication state of the # current user changes. get_permissions_manager().user_manager.on_trait_event(adapter._refresh, 'user_authenticated') return adapter def adapt(self): """Try and adapt the proxied object and return the adapted object if successful. If None is returned a proxy wrapper will be created. """ return None def setattr(self, name, value): """The default implementation to set an attribute of the proxied object. """ setattr(self.proxied, name, value) def get_enabled(self): """Get the enabled state of the proxied object.""" raise NotImplementedError def set_enabled(self, value): """Set the enabled state of the proxied object.""" raise NotImplementedError def update_enabled(self, value): """Update the proxied object after a possible new value of the enabled attribute. """ if get_permissions_manager().check_permissions(*self.permissions): self.set_enabled(value) if not self.show: self.set_visible(self._desired_visible) else: self.set_enabled(False) if not self.show: self.set_visible(False) # Save the desired value so that it can be checked if the user becomes # authenticated. self._desired_enabled = value def get_visible(self): """Get the visible state of the proxied object.""" raise NotImplementedError def set_visible(self, value): """Set the visible state of the proxied object.""" raise NotImplementedError def update_visible(self, value): """Update the proxied object after a possible new value of the visible attribute. """ if self.show: self.set_visible(value) elif get_permissions_manager().check_permissions(*self.permissions): self.set_visible(value) else: self.set_visible(False) # Save the desired value so that it can be checked if the user becomes # authenticated. self._desired_visible = value def _refresh(self, user): """Invoked whenever the current user's authorisation state changes.""" if not self.show: self.update_visible(self._desired_visible) self.update_enabled(self._desired_enabled) apptools-4.1.0/apptools/permissions/__init__.py0000644000175100001440000000032311674464005022670 0ustar ischnellusers00000000000000# Copyright (c) 2007-2011 by Enthought, Inc. # All rights reserved. """ Supports limiting access to parts of an application to authorised users. Part of the AppTools project of the Enthought Tool Suite. """ apptools-4.1.0/apptools/permissions/i_policy_manager.py0000644000175100001440000000341411674464005024436 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from pyface.action.api import Action from traits.api import Instance, Interface, List class IPolicyManager(Interface): """The interface implemented by a policy manager. A policy manager defines how permissions are assigned to users and stored. A default policy manager is provided, but it may be replaced using the permissions manager.""" # The list of PyFace policy management actions implemented by this policy. management_actions = List(Instance(Action)) # The list of permissions assigned to the current user. user_permissions = List(Instance('apptools.permissions.api.Permission')) def bootstrapping(self): """Return True if the policy manager is bootstrapping. Typically this is when no permissions have been assigned.""" def load_user(self, user): """Load the policy for the given user. user is an object that implements IUser. If it is None then unload the policy for the current user.""" def register_permission(self, permission): """Register the given permission defined by the application.""" apptools-4.1.0/apptools/permissions/package_globals.py0000644000175100001440000000243611674464005024236 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # The permissions manager. _permissions_manager = None def get_permissions_manager(): """Return the IPermissionsManager implementation, creating a PermissionsManager instance if no other implementation has been set.""" global _permissions_manager if _permissions_manager is None: from permissions_manager import PermissionsManager _permissions_manager = PermissionsManager() return _permissions_manager def set_permissions_manager(permissions_manager): """Set the IPermissionsManager implementation to use.""" global _permissions_manager _permissions_manager = permissions_manager apptools-4.1.0/apptools/permissions/adapters/0000755000175100001440000000000011674464005022364 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/permissions/adapters/qt4_widget.py0000644000175100001440000000405211674464005025012 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Major library imports. from pyface.qt import QtGui # Enthought library imports. from apptools.permissions.adapter_base import AdapterBase class QWidgetAdapter(AdapterBase): """This is the adapter for PyQt QWidget instances.""" def adapt(self): """Reimplemented to adapt the proxied object.""" # Replace the real methods. self.proxied.setEnabled = self.update_enabled self.proxied.setVisible = self.update_visible self.proxied.hide = self._hide self.proxied.show = self._show return self.proxied def get_enabled(self): """Get the enabled state of the proxied object.""" return self.proxied.isEnabled() def set_enabled(self, value): """Set the enabled state of the proxied object.""" QtGui.QWidget.setEnabled(self.proxied, value) def get_visible(self): """Get the visible state of the proxied object.""" return self.proxied.isVisible() def set_visible(self, value): """Set the visible state of the proxied object.""" QtGui.QWidget.setVisible(self.proxied, value) def _hide(self): """The replacement QWidget.hide() implementation.""" self.update_visible(False) def _show(self): """The replacement QWidget.show() implementation.""" self.update_visible(True) AdapterBase.register_adapter(QWidgetAdapter, QtGui.QWidget) apptools-4.1.0/apptools/permissions/adapters/wx_window.py0000644000175100001440000000327211674464005024767 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Major library imports. import wx # Enthought library imports. from apptools.permissions.adapter_base import AdapterBase class wxWindowAdapter(AdapterBase): """This is the adapter for wx Window instances.""" def adapt(self): """Reimplemented to adapt the proxied object.""" # Replace the real methods. self.proxied.Enable = self.update_enabled self.proxied.Show = self.update_visible return self.proxied def get_enabled(self): """Get the enabled state of the proxied object.""" return self.proxied.IsEnabled() def set_enabled(self, value): """Set the enabled state of the proxied object.""" wx.Window.Enable(self.proxied, value) def get_visible(self): """Get the visible state of the proxied object.""" return self.proxied.IsShown() def set_visible(self, value): """Set the visible state of the proxied object.""" wx.Window.Show(self.proxied, value) AdapterBase.register_adapter(wxWindowAdapter, wx.Window) apptools-4.1.0/apptools/permissions/adapters/__init__.py0000644000175100001440000000010411674464005024470 0ustar ischnellusers00000000000000# Copyright (c) 2007-2011 by Enthought, Inc. # All rights reserved. apptools-4.1.0/apptools/permissions/adapters/pyface_action.py0000644000175100001440000000344511674464005025550 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from apptools.permissions.adapter_base import AdapterBase from pyface.action.api import Action class ActionAdapter(AdapterBase): """This is the adapter for PyFace actions.""" def get_enabled(self): """Get the enabled state of the proxied object.""" return self.proxied.enabled def set_enabled(self, value): """Set the enabled state of the proxied object.""" self.proxied.enabled = value def get_visible(self): """Get the visible state of the proxied object.""" return self.proxied.visible def set_visible(self, value): """Set the visible state of the proxied object.""" self.proxied.visible = value def setattr(self, name, value): """Reimplemented to intercept the setting of the enabled and visible attributes of the proxied action. """ if name == 'enabled': self.update_enabled(value) elif name == 'visible': self.update_visible(value) else: super(ActionAdapter, self).setattr(name, value) AdapterBase.register_adapter(ActionAdapter, Action) apptools-4.1.0/apptools/permissions/secure_proxy.py0000644000175100001440000001512511674464005023666 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.etsconfig.api import ETSConfig from traitsui.api import Handler # Local imports. from adapter_base import AdapterBase from package_globals import get_permissions_manager # Register the bundled adapters. from adapters import pyface_action if ETSConfig.toolkit == 'wx': from adapters import wx_window elif ETSConfig.toolkit == 'qt4': from adapters import qt4_widget class SecureProxy(object): """The SecureProxy class is a wrapper for an object whose enabled and visible states can be managed. It attaches one or more permissions to the object and enables and shows the object only if those permissions allow it. In all other respects it behaves exactly like the object. It is based on Tomer Filiba's Object Proxy cookbook recipe.""" __slots__ = ('_ets', '__weakref__') def __init__(self, proxied, permissions, show=True): """Initialise the instance. proxied is the object whose enabled and visible states are managed according to the permissions of the current user. permissions is a list of permissions to attach to the object. show is set if the proxied object should be visible when it is disabled.""" adapter = object.__getattribute__(self, '_ets') # Correct the current values. if not show: adapter.update_visible(adapter.get_visible()) adapter.update_enabled(adapter.get_enabled()) # Proxying (special cases). def __getattribute__(self, name): return getattr(object.__getattribute__(self, '_ets').proxied, name) def __delattr__(self, name): delattr(object.__getattribute__(self, '_ets').proxied, name) def __setattr__(self, name, value): object.__getattribute__(self, '_ets').setattr(name, value) def __nonzero__(self): return bool(object.__getattribute__(self, '_ets').proxied) def __str__(self): return str(object.__getattribute__(self, '_ets').proxied) def __repr__(self): return repr(object.__getattribute__(self, '_ets').proxied) # Factories. _special_names = [ '__abs__', '__add__', '__and__', '__call__', '__cmp__', '__coerce__', '__contains__', '__delitem__', '__delslice__', '__div__', '__divmod__', '__eq__', '__float__', '__floordiv__', '__ge__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__hex__', '__iadd__', '__iand__', '__idiv__', '__idivmod__', '__ifloordiv__', '__ilshift__', '__imod__', '__imul__', '__int__', '__invert__', '__ior__', '__ipow__', '__irshift__', '__isub__', '__iter__', '__itruediv__', '__ixor__', '__le__', '__len__', '__long__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__oct__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdiv__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rfloorfiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setitem__', '__setslice__', '__sub__', '__truediv__', '__xor__', 'next', ] @classmethod def _ets_class_proxy(cls, theclass): """Creates a proxy for the given class.""" def make_method(name): def method(self, *args, **kw): return getattr(object.__getattribute__(self, '_ets').proxied, name)(*args, **kw) return method namespace = {} for name in cls._special_names: if hasattr(theclass, name) and not hasattr(cls, name): namespace[name] = make_method(name) return type("%s(%s)" % (cls.__name__, theclass.__name__), (cls,), namespace) def __new__(cls, proxied, permissions, show=True): """Apply a set of permissions to an object. This may be done by creating a proxy or by modifying the object in situ depending on its type. """ # Create the adapter. adapter = AdapterBase.factory(proxied, permissions, show) # Try and adapt the object itself. adapted = adapter.adapt() if adapted is None: # Create a wrapper for the object. The cache is unique per # deriving class to ensure there are no clashes with any # sub-classes with the same name. try: cache = cls.__dict__['_ets_cache'] except KeyError: cls._ets_cache = cache = {} pclass = proxied.__class__ try: theclass = cache[pclass] except KeyError: cache[pclass] = theclass = cls._ets_class_proxy(pclass) adapted = object.__new__(theclass) # Save the adapter in the adapted object. object.__setattr__(adapted, '_ets', adapter) else: # Correct the current values. if not show: adapter.update_visible(adapter.get_visible()) adapter.update_enabled(adapter.get_enabled()) # Save the adapter in the adapted object. adapted._ets = adapter return adapted class SecureHandler(Handler): """The SecureHandler class is a sub-class of the TraitsUI Handler class that ensures that the enabled and visible state of the items of a TraitsUI view are updated when the user's authorisation state changes. """ def __init__(self, **traits): """Initialise the object.""" super(SecureHandler, self).__init__(**traits) get_permissions_manager().user_manager.on_trait_event(self._refresh, 'user_authenticated') def init_info(self, info): """Reimplemented to save the UIInfo object.""" self._info = info def _refresh(self): """Invoked whenever the current user's authorisation state changes.""" # FIXME: This is (currently) an internal method. self._info.ui._evaluate_when() apptools-4.1.0/apptools/preferences/0000755000175100001440000000000011674464005020507 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/preferences/i_preferences.py0000644000175100001440000001107711674464005023700 0ustar ischnellusers00000000000000""" The interface for a node in a preferences hierarchy. """ # Enthought library imports. from traits.api import Instance, Interface, Str class IPreferences(Interface): """ The interface for a node in a preferences hierarchy. """ # The absolute path to this node from the root node (the empty string if # this node *is* the root node). path = Str # The parent node (None if this node *is* the root node). parent = Instance('IPreferences') # The name of the node relative to its parent (the empty string if this # node *is* the root node). name = Str #### Methods where 'path' refers to a preference #### def get(self, path, default=None, inherit=False): """ Get the value of the preference at the specified path. If no value exists for the path (or any part of the path does not exist) then return the default value. Preference values are *always* returned as strings. e.g:: preferences.set('acme.ui.bgcolor', 'blue') preferences.get('acme.ui.bgcolor') -> 'blue' preferences.set('acme.ui.width', 100) preferences.get('acme.ui.width') -> '100' preferences.set('acme.ui.visible', True) preferences.get('acme.ui.visible') -> 'True' If 'inherit' is True then we allow 'inherited' preference values. e.g. If we are looking up:: 'acme.ui.widget.bgcolor' and it does not exist then we will also try:: 'acme.ui.bgcolor' 'acme.bgcolor' 'bgcolor' Raise a 'ValueError' exception if the path is the empty string. """ def remove(self, path): """ Remove the preference at the specified path. Does nothing if no value exists for the path (or any part of the path does not exist. Raise a 'ValueError' exception if the path is the empty string. e.g.:: preferences.remove('acme.ui.bgcolor') """ def set(self, path, value): """ Set the value of the preference at the specified path. Any missing nodes are created automatically. Primitive Python types can be set, but preferences are *always* stored and returned as strings. e.g:: preferences.set('acme.ui.bgcolor', 'blue') preferences.get('acme.ui.bgcolor') -> 'blue' preferences.set('acme.ui.width', 100) preferences.get('acme.ui.width') -> '100' preferences.set('acme.ui.visible', True) preferences.get('acme.ui.visible') -> 'True' Raise a 'ValueError' exception if the path is the empty string. """ #### Methods where 'path' refers to a node #### def clear(self, path=''): """ Remove all preference from the node at the specified path. If the path is the empty string (the default) then remove the preferences in *this* node. This does not affect any of the node's children. e.g. To clear the preferences out of a node directly:: preferences.clear() Or to clear the preferences of a node at a given path:: preferences.clear('acme.ui') """ def keys(self, path=''): """ Return the preference keys of the node at the specified path. If the path is the empty string (the default) then return the preference keys of *this* node. e.g:: keys = preferences.keys('acme.ui') """ def node(self, path=''): """ Return the node at the specified path. If the path is the empty string (the default) then return *this* node. Any missing nodes are created automatically. e.g:: node = preferences.node('acme.ui') bgcolor = node.get('bgcolor') """ def node_exists(self, path=''): """ Return True if the node at the specified path exists If the path is the empty string (the default) then return True. e.g:: exists = preferences.exists('acme.ui') """ def node_names(self, path=''): """ Return the names of the children of the node at the specified path. If the path is the empty string (the default) then return the names of the children of *this* node. e.g:: names = preferences.node_names('acme.ui') """ #### Persistence methods #### def flush(self): """ Force any changes in the node to the backing store. This includes any changes to the node's descendants. """ #### EOF ###################################################################### apptools-4.1.0/apptools/preferences/tests/0000755000175100001440000000000011674464005021651 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/preferences/tests/py_config_file_test_case.py0000644000175100001440000001551511674464005027240 0ustar ischnellusers00000000000000""" Tests for Python-esque '.ini' files. """ # Standard library imports. import os, tempfile, unittest from os.path import join # Major package imports. from pkg_resources import resource_filename # Enthought library imports. from py_config_file import PyConfigFile # This module's package. PKG = 'apptools.preferences.tests' class PyConfigFileTestCase(unittest.TestCase): """ Tests for Python-esque '.ini' files. """ ########################################################################### # 'TestCase' interface. ########################################################################### def setUp(self): """ Prepares the test fixture before each test method is called. """ # The filenames of the example preferences files. self.example = resource_filename(PKG, 'py_config_example.ini') self.example_2 = resource_filename(PKG, 'py_config_example_2.ini') return def tearDown(self): """ Called immediately after each test method has been called. """ return ########################################################################### # Tests. ########################################################################### def test_load_from_filename(self): """ load from filename """ config = PyConfigFile(self.example) self.assertEqual('blue', config['acme.ui']['bgcolor']) self.assertEqual(50, config['acme.ui']['width']) self.assertEqual(1.0, config['acme.ui']['ratio']) self.assertEqual(True, config['acme.ui']['visible']) self.assertEqual({'a' : 1, 'b' : 2}, config['acme.ui']['foo']) self.assertEqual([1, 2, 3, 4], config['acme.ui']['bar']) self.assertEqual((1, 'a', 6, 4), config['acme.ui']['baz']) self.assertEqual('red', config['acme.ui.other']['fred']) self.assertEqual(100, config['acme.ui.other']['wilma']) self.assertEqual(90, config['tds.foogle']['joe']) self.assertEqual("meerkat", config['simples']['animal']) return def test_load_from_file(self): """ load from file """ config = PyConfigFile(file(self.example)) self.assertEqual('blue', config['acme.ui']['bgcolor']) self.assertEqual(50, config['acme.ui']['width']) self.assertEqual(1.0, config['acme.ui']['ratio']) self.assertEqual(True, config['acme.ui']['visible']) self.assertEqual({'a' : 1, 'b' : 2}, config['acme.ui']['foo']) self.assertEqual([1, 2, 3, 4], config['acme.ui']['bar']) self.assertEqual((1, 'a', 6, 4), config['acme.ui']['baz']) self.assertEqual('red', config['acme.ui.other']['fred']) self.assertEqual(100, config['acme.ui.other']['wilma']) self.assertEqual(90, config['tds.foogle']['joe']) self.assertEqual("meerkat", config['simples']['animal']) return def test_save(self): """ save """ config = PyConfigFile(file(self.example)) self.assertEqual('blue', config['acme.ui']['bgcolor']) self.assertEqual(50, config['acme.ui']['width']) self.assertEqual(1.0, config['acme.ui']['ratio']) self.assertEqual(True, config['acme.ui']['visible']) self.assertEqual({'a' : 1, 'b' : 2}, config['acme.ui']['foo']) self.assertEqual([1, 2, 3, 4], config['acme.ui']['bar']) self.assertEqual('red', config['acme.ui.other']['fred']) self.assertEqual(100, config['acme.ui.other']['wilma']) self.assertEqual(90, config['tds.foogle']['joe']) self.assertEqual("meerkat", config['simples']['animal']) # Save the config to another file. tmpdir = tempfile.mkdtemp() tmp = join(tmpdir, 'tmp.ini') config.save(tmp) try: self.assert_(os.path.exists(tmp)) # Make sure we can read the file back in and that we get the same # values! config = PyConfigFile(file(tmp)) self.assertEqual('blue', config['acme.ui']['bgcolor']) self.assertEqual(50, config['acme.ui']['width']) self.assertEqual(1.0, config['acme.ui']['ratio']) self.assertEqual(True, config['acme.ui']['visible']) self.assertEqual({'a' : 1, 'b' : 2}, config['acme.ui']['foo']) self.assertEqual([1, 2, 3, 4], config['acme.ui']['bar']) self.assertEqual((1, 'a', 6, 4), config['acme.ui']['baz']) self.assertEqual('red', config['acme.ui.other']['fred']) self.assertEqual(100, config['acme.ui.other']['wilma']) self.assertEqual(90, config['tds.foogle']['joe']) self.assertEqual("meerkat", config['simples']['animal']) finally: # Clean up! os.remove(tmp) os.removedirs(tmpdir) return def test_load_multiple_files(self): """ load multiple files """ config = PyConfigFile(self.example) self.assertEqual('blue', config['acme.ui']['bgcolor']) self.assertEqual(50, config['acme.ui']['width']) self.assertEqual(1.0, config['acme.ui']['ratio']) self.assertEqual(True, config['acme.ui']['visible']) self.assertEqual({'a' : 1, 'b' : 2}, config['acme.ui']['foo']) self.assertEqual([1, 2, 3, 4], config['acme.ui']['bar']) self.assertEqual((1, 'a', 6, 4), config['acme.ui']['baz']) self.assertEqual('red', config['acme.ui.other']['fred']) self.assertEqual(100, config['acme.ui.other']['wilma']) self.assertEqual(90, config['tds.foogle']['joe']) self.assertEqual("meerkat", config['simples']['animal']) # Load another file. config.load(self.example_2) # Make sure we still have the unchanged values... self.assertEqual('red', config['acme.ui']['bgcolor']) self.assertEqual(50, config['acme.ui']['width']) self.assertEqual(1.0, config['acme.ui']['ratio']) self.assertEqual(True, config['acme.ui']['visible']) self.assertEqual({'a' : 1, 'b' : 2}, config['acme.ui']['foo']) self.assertEqual([1, 2, 3, 4], config['acme.ui']['bar']) self.assertEqual((1, 'a', 6, 4), config['acme.ui']['baz']) self.assertEqual('red', config['acme.ui.other']['fred']) self.assertEqual(100, config['acme.ui.other']['wilma']) self.assertEqual(90, config['tds.foogle']['joe']) self.assertEqual("meerkat", config['simples']['animal']) # ... and the values that were overwritten... self.assertEqual('red', config['acme.ui']['bgcolor']) # ... and that we have the new ones. self.assertEqual(42, config['acme.ui']['bazzle']) # ... and that the new ones can refer to the old ones! self.assertEqual(180, config['acme.ui']['blimey']) return # Entry point for stand-alone testing. if __name__ == '__main__': unittest.main() #### EOF ###################################################################### apptools-4.1.0/apptools/preferences/tests/py_config_example.ini0000644000175100001440000000137711674464005026052 0ustar ischnellusers00000000000000[acme.ui] bgcolor = "blue" width = 50 ratio = 1.0 visible = True foo = { 'a' : 1, 'b' : 2 } bar = [ 1, 2, 3, 4 ] baz = ( 1, 'a', 6, 4 ) [acme.ui.splash_screen] image = "splash" fgcolor = "red" # You can also reference a previous setting as in the following example, but # note that if you *write* these settings back out again, then any reference # to another setting is lost - just the literal value gets written. # # e.g. The following section would be written as:- # # [acme.ui.other] # fred = "red" # wilma = 100 # [acme.ui.other] fred = acme.ui.splash_screen.fgcolor wilma = acme.ui.foo['a'] + 99 # To show that not every section needs to be from the same root! [tds.foogle] joe = 90 # A non-dotted section name. [simples] animal = "meerkat"apptools-4.1.0/apptools/preferences/tests/example.ini0000644000175100001440000000031011674464005023777 0ustar ischnellusers00000000000000[acme.ui] bgcolor = blue width = 50 ratio = 1.0 visible = True description = 'acme ui' offsets = "[1, 2, 3, 4]" names = "['joe', 'fred', 'jane']" [acme.ui.splash_screen] image = splash fgcolor = red apptools-4.1.0/apptools/preferences/tests/preferences_helper_test_case.py0000644000175100001440000003770611674464005030132 0ustar ischnellusers00000000000000""" Tests for the preferences helper. """ # Standard library imports. import unittest # Major package imports. from pkg_resources import resource_filename # Enthought library imports. from apptools.preferences.api import Preferences, PreferencesHelper from apptools.preferences.api import ScopedPreferences from apptools.preferences.api import set_default_preferences from traits.api import Any, Bool, HasTraits, Int, Float, List, Str from traits.api import Unicode def listener(obj, trait_name, old, new): """ A useful trait change handler for testing! """ listener.obj = obj listener.trait_name = trait_name listener.old = old listener.new = new return # This module's package. PKG = 'apptools.preferences.tests' class PreferencesHelperTestCase(unittest.TestCase): """ Tests for the preferences helper. """ ########################################################################### # 'TestCase' interface. ########################################################################### def setUp(self): """ Prepares the test fixture before each test method is called. """ self.preferences = set_default_preferences(Preferences()) # The filename of the example preferences file. self.example = resource_filename(PKG, 'example.ini') return def tearDown(self): """ Called immediately after each test method has been called. """ return ########################################################################### # Tests. ########################################################################### def test_class_scope_preferences_path(self): """ class scope preferences path """ p = self.preferences p.load(self.example) class AcmeUIPreferencesHelper(PreferencesHelper): """ A helper! """ # The path to the preferences node that contains our preferences. preferences_path = 'acme.ui' # The traits that we want to initialize from preferences. bgcolor = Str width = Int ratio = Float visible = Bool description = Unicode offsets = List(Int) names = List(Str) helper = AcmeUIPreferencesHelper() helper.on_trait_change(listener) # Make sure the helper was initialized properly. self.assertEqual('blue', helper.bgcolor) self.assertEqual(50, helper.width) self.assertEqual(1.0, helper.ratio) self.assertEqual(True, helper.visible) self.assertEqual(u'acme ui', helper.description) self.assertEqual([1, 2, 3, 4], helper.offsets) self.assertEqual(['joe', 'fred', 'jane'], helper.names) # Make sure we can set the preference via the helper... helper.bgcolor = 'yellow' self.assertEqual('yellow', p.get('acme.ui.bgcolor')) self.assertEqual('yellow', helper.bgcolor) # ... and that the correct trait change event was fired. self.assertEqual(helper, listener.obj) self.assertEqual('bgcolor', listener.trait_name) self.assertEqual('blue', listener.old) self.assertEqual('yellow', listener.new) # Make sure we can set the preference via the preferences node... p.set('acme.ui.bgcolor', 'red') self.assertEqual('red', p.get('acme.ui.bgcolor')) self.assertEqual('red', helper.bgcolor) # ... and that the correct trait change event was fired. self.assertEqual(helper, listener.obj) self.assertEqual('bgcolor', listener.trait_name) self.assertEqual('yellow', listener.old) self.assertEqual('red', listener.new) return def test_instance_scope_preferences_path(self): """ instance scope preferences path """ p = self.preferences p.load(self.example) class AcmeUIPreferencesHelper(PreferencesHelper): """ A helper! """ # The traits that we want to initialize from preferences. bgcolor = Str width = Int ratio = Float visible = Bool description = Unicode offsets = List(Int) names = List(Str) helper = AcmeUIPreferencesHelper(preferences_path='acme.ui') helper.on_trait_change(listener) # Make sure the helper was initialized properly. self.assertEqual('blue', helper.bgcolor) self.assertEqual(50, helper.width) self.assertEqual(1.0, helper.ratio) self.assertEqual(True, helper.visible) self.assertEqual(u'acme ui', helper.description) self.assertEqual([1, 2, 3, 4], helper.offsets) self.assertEqual(['joe', 'fred', 'jane'], helper.names) # Make sure we can set the preference via the helper... helper.bgcolor = 'yellow' self.assertEqual('yellow', p.get('acme.ui.bgcolor')) self.assertEqual('yellow', helper.bgcolor) # ... and that the correct trait change event was fired. self.assertEqual(helper, listener.obj) self.assertEqual('bgcolor', listener.trait_name) self.assertEqual('blue', listener.old) self.assertEqual('yellow', listener.new) # Make sure we can set the preference via the preferences node... p.set('acme.ui.bgcolor', 'red') self.assertEqual('red', p.get('acme.ui.bgcolor')) self.assertEqual('red', helper.bgcolor) # ... and that the correct trait change event was fired. self.assertEqual(helper, listener.obj) self.assertEqual('bgcolor', listener.trait_name) self.assertEqual('yellow', listener.old) self.assertEqual('red', listener.new) return def test_default_values(self): """ default values """ p = self.preferences class AcmeUIPreferencesHelper(PreferencesHelper): """ A helper! """ # The path to the preferences node that contains our preferences. preferences_path = 'acme.ui' # The traits that we want to initialize from preferences. bgcolor = Str('blue') width = Int(50) ratio = Float(1.0) visible = Bool(True) description = Unicode(u'description') offsets = List(Int, [1, 2, 3, 4]) names = List(Str, ['joe', 'fred', 'jane']) helper = AcmeUIPreferencesHelper() # Make sure the helper was initialized properly. self.assertEqual('blue', helper.bgcolor) self.assertEqual(50, helper.width) self.assertEqual(1.0, helper.ratio) self.assertEqual(True, helper.visible) self.assertEqual(u'description', helper.description) self.assertEqual([1, 2, 3, 4], helper.offsets) self.assertEqual(['joe', 'fred', 'jane'], helper.names) return def test_no_preferences_path(self): """ no preferences path """ p = self.preferences p.load(self.example) class AcmeUIPreferencesHelper(PreferencesHelper): """ A helper! """ # The traits that we want to initialize from preferences. bgcolor = Str width = Int ratio = Float visible = Bool description = Unicode offsets = List(Int) names = List(Str) # Cannot create a helper with a preferences path. self.failUnlessRaises(SystemError, AcmeUIPreferencesHelper) return def test_sync_trait(self): """ sync trait """ class Widget(HasTraits): """ A widget! """ background_color = Str w = Widget() w.on_trait_change(listener) p = self.preferences p.load(self.example) class AcmeUIPreferencesHelper(PreferencesHelper): """ A helper! """ # The path to the preferences node that contains our preferences. preferences_path = 'acme.ui' # The traits that we want to initialize from preferences. bgcolor = Str width = Int ratio = Float visible = Bool description = Unicode offsets = List(Int) names = List(Str) helper = AcmeUIPreferencesHelper() helper.sync_trait('bgcolor', w, 'background_color') # Make sure the helper was initialized properly. self.assertEqual('blue', helper.bgcolor) self.assertEqual(50, helper.width) self.assertEqual(1.0, helper.ratio) self.assertEqual(True, helper.visible) self.assertEqual(u'acme ui', helper.description) self.assertEqual([1, 2, 3, 4], helper.offsets) self.assertEqual(['joe', 'fred', 'jane'], helper.names) self.assertEqual('blue', w.background_color) # Make sure we can set the preference via the helper... helper.bgcolor = 'yellow' self.assertEqual('yellow', p.get('acme.ui.bgcolor')) self.assertEqual('yellow', helper.bgcolor) self.assertEqual('yellow', w.background_color) # ... and that the correct trait change event was fired. self.assertEqual(w, listener.obj) self.assertEqual('background_color', listener.trait_name) self.assertEqual('blue', listener.old) self.assertEqual('yellow', listener.new) # Make sure we can set the preference via the preferences node... p.set('acme.ui.bgcolor', 'red') self.assertEqual('red', p.get('acme.ui.bgcolor')) self.assertEqual('red', helper.bgcolor) self.assertEqual('red', w.background_color) # ... and that the correct trait change event was fired. self.assertEqual(w, listener.obj) self.assertEqual('background_color', listener.trait_name) self.assertEqual('yellow', listener.old) self.assertEqual('red', listener.new) return def test_scoped_preferences(self): """ scoped preferences """ p = set_default_preferences(ScopedPreferences()) # Set a preference value in the default scope. p.set('default/acme.ui.bgcolor', 'blue') class AcmeUIPreferencesHelper(PreferencesHelper): """ A helper! """ # The path to the preferences node that contains our preferences. preferences_path = 'acme.ui' # The traits that we want to initialize from preferences. bgcolor = Str # A trait for a preference that does not exist yet. name = Str helper = AcmeUIPreferencesHelper() # Make sure the trait is set! self.assertEqual('blue', helper.bgcolor) # And that the non-existent trait gets the default value. self.assertEqual('', helper.name) return def test_preference_not_in_file(self): """ preference not in file """ class AcmeUIPreferencesHelper(PreferencesHelper): """ A helper! """ # The path to the preferences node that contains our preferences. preferences_path = 'acme.ui' # A trait that has no corresponding value in the file. title = Str('Acme') helper = AcmeUIPreferencesHelper() # Make sure the trait is set! self.assertEqual('Acme', helper.title) # Set a new value. helper.title = 'Acme Plus' # Make sure the trait is set! self.assertEqual('Acme Plus', helper.title) self.assertEqual('Acme Plus', self.preferences.get('acme.ui.title')) return def test_preferences_node_changed(self): """ preferences node changed """ p = self.preferences p.load(self.example) class AcmeUIPreferencesHelper(PreferencesHelper): """ A helper! """ # The path to the preferences node that contains our preferences. preferences_path = 'acme.ui' # The traits that we want to initialize from preferences. bgcolor = Str width = Int ratio = Float visible = Bool description = Unicode offsets = List(Int) names = List(Str) helper = AcmeUIPreferencesHelper() # We only listen to some of the traits so the testing is easier. helper.on_trait_change(listener, ['bgcolor', 'width']) # Create a new preference node. p1 = Preferences() p1.load(self.example) p1.set('acme.ui.bgcolor', 'red') p1.set('acme.ui.width', 40) # Set the new preferences helper.preferences = p1 # Test event handling. self.assertEqual(helper, listener.obj) self.assertEqual('width', listener.trait_name) self.assertEqual(50, listener.old) self.assertEqual(40, listener.new) # Test re-initialization. self.assertEqual(helper.bgcolor, 'red') self.assertEqual(helper.width, 40) # Test event handling. p1.set('acme.ui.bgcolor', 'black') self.assertEqual(helper, listener.obj) self.assertEqual('bgcolor', listener.trait_name) self.assertEqual('red', listener.old) self.assertEqual('black', listener.new) # This should not trigger any new changes since we are setting values # on the old preferences node. p.set('acme.ui.bgcolor', 'white') self.assertEqual(helper, listener.obj) self.assertEqual('bgcolor', listener.trait_name) self.assertEqual('red', listener.old) self.assertEqual('black', listener.new) return def test_nested_set_in_trait_change_handler(self): """ nested set in trait change handler """ p = self.preferences p.load(self.example) class AcmeUIPreferencesHelper(PreferencesHelper): """ A helper! """ # The traits that we want to initialize from preferences. bgcolor = Str width = Int ratio = Float visible = Bool description = Unicode offsets = List(Int) names = List(Str) # When the width changes, change the ratio. def _width_changed(self, trait_name, old, new): """ Static trait change handler. """ self.ratio = 3.0 return helper = AcmeUIPreferencesHelper(preferences_path='acme.ui') # Make sure the helper was initialized properly. self.assertEqual('blue', helper.bgcolor) self.assertEqual(50, helper.width) self.assertEqual(1.0, helper.ratio) self.assertEqual(True, helper.visible) self.assertEqual(u'acme ui', helper.description) self.assertEqual([1, 2, 3, 4], helper.offsets) self.assertEqual(['joe', 'fred', 'jane'], helper.names) # Change the width via the preferences node. This should cause the # ratio to get set via the static trait change handler on the helper. p.set('acme.ui.width', 42) self.assertEqual(42, helper.width) self.assertEqual('42', p.get('acme.ui.width')) # Did the ratio get changed? self.assertEqual(3.0, helper.ratio) self.assertEqual('3.0', p.get('acme.ui.ratio')) return # fixme: No comments - nice work... I added the doc string and the 'return' # to be compatible with the rest of the module. Interns please note correct # procedure when modifying existing code. If in doubt, ask a developer. def test_unevaluated_strings(self): """ unevaluated strings """ p = self.preferences p.load(self.example) class AcmeUIPreferencesHelper(PreferencesHelper): width = Any(is_str=True) helper = AcmeUIPreferencesHelper(preferences_path='acme.ui') self.assertEqual('50', helper.width) return # Entry point for stand-alone testing. if __name__ == '__main__': unittest.main() #### EOF ###################################################################### apptools-4.1.0/apptools/preferences/tests/py_config_example_2.ini0000644000175100001440000000010311674464005026255 0ustar ischnellusers00000000000000[acme.ui] bgcolor = "red" bazzle = 42 blimey = tds.foogle.joe * 2 apptools-4.1.0/apptools/preferences/tests/scoped_preferences_test_case.py0000644000175100001440000003246411674464005030124 0ustar ischnellusers00000000000000""" Tests for scoped preferences. """ # Standard library imports. import os, tempfile, unittest from os.path import join # Major package imports. from pkg_resources import resource_filename # Enthought library imports. from apptools.preferences.api import Preferences, ScopedPreferences # Local imports. from preferences_test_case import PreferencesTestCase # This module's package. PKG = 'apptools.preferences.tests' class ScopedPreferencesTestCase(PreferencesTestCase): """ Tests for the scoped preferences. """ ########################################################################### # 'TestCase' interface. ########################################################################### def setUp(self): """ Prepares the test fixture before each test method is called. """ self.preferences = ScopedPreferences() # The filename of the example preferences file. self.example = resource_filename(PKG, 'example.ini') # A temporary directory that can safely be written to. self.tmpdir = tempfile.mkdtemp() return def tearDown(self): """ Called immediately after each test method has been called. """ # Remove the temporary directory. os.removedirs(self.tmpdir) return ########################################################################### # Tests overridden from 'PreferencesTestCase'. ########################################################################### def test_node(self): """ node """ p = self.preferences # Try an empty path. self.assertEqual(p, p.node()) # Try a simple path. node = p.node('acme') self.assertNotEqual(None, node) self.assertEqual('acme', node.name) self.assertEqual('acme', node.path) self.assertEqual(p.node('application/'), node.parent) # Make sure we get the same node each time we ask for it! self.assertEqual(node, p.node('acme')) # Try a nested path. node = p.node('acme.ui') self.assertNotEqual(None, node) self.assertEqual('ui', node.name) self.assertEqual('acme.ui', node.path) self.assertEqual(p.node('application/acme'), node.parent) # And just to be sure, a really nested path. node = p.node('acme.ui.splash_screen') self.assertNotEqual(None, node) self.assertEqual('splash_screen', node.name) self.assertEqual('acme.ui.splash_screen', node.path) self.assertEqual(p.node('application/acme.ui'), node.parent) return def test_save(self): """ save """ p = self.preferences # Get the application scope. application = p.node('application/') tmp = join(self.tmpdir, 'test.ini') application.filename = tmp # Set a value. p.set('acme.ui.bgcolor', 'red') # Save all scopes. p.save() # Make sure a file was written. self.assertEqual(True, os.path.exists(tmp)) # Load the 'ini' file into a new preferences node and make sure the # preference is in there. p = Preferences() p.load(tmp) self.assertEqual('red', p.get('acme.ui.bgcolor')) # Cleanup. os.remove(tmp) return ########################################################################### # Tests. ########################################################################### def test_ability_to_specify_primary_scope(self): preferences = ScopedPreferences( scopes = [ Preferences(name='a'), Preferences(name='b'), Preferences(name='c') ], primary_scope_name = 'b' ) # This should set the prefrrence in the primary scope. preferences.set('acme.foo', 'bar') # Look it up specifically in the primary scope. self.assertEqual('bar', preferences.get('b/acme.foo')) return def test_builtin_scopes(self): """ builtin scopes """ p = self.preferences # Make sure the default built-in scopes get created. self.assertEqual(True, p.node_exists('application/')) self.assertEqual(True, p.node_exists('default/')) return def test_get_and_set_in_specific_scope(self): """ get and set in specific scope """ p = self.preferences # Set a preference and make sure we can get it again! p.set('default/acme.ui.bgcolor', 'red') self.assertEqual('red', p.get('default/acme.ui.bgcolor')) return def test_clear_in_specific_scope(self): """ clear in specific scope """ p = self.preferences # Set a value in both the application and default scopes. p.set('application/acme.ui.bgcolor', 'red') p.set('default/acme.ui.bgcolor', 'yellow') # Make sure when we look it up we get the one in first scope in the # lookup order. self.assertEqual('red', p.get('acme.ui.bgcolor')) # Now clear out the application scope. p.clear('application/acme.ui') self.assertEqual(0, len(p.keys('application/acme.ui'))) # We should now get the value from the default scope. self.assertEqual('yellow', p.get('acme.ui.bgcolor')) return def test_remove_in_specific_scope(self): """ remove in specific scope """ p = self.preferences # Set a value in both the application and default scopes. p.set('application/acme.ui.bgcolor', 'red') p.set('default/acme.ui.bgcolor', 'yellow') # Make sure when we look it up we get the one in first scope in the # lookup order. self.assertEqual('red', p.get('acme.ui.bgcolor')) # Now remove it from the application scope. p.remove('application/acme.ui.bgcolor') # We should now get the value from the default scope. self.assertEqual('yellow', p.get('acme.ui.bgcolor')) return def test_keys_in_specific_scope(self): """ keys in specific scope """ p = self.preferences # It should be empty to start with! self.assertEqual([], p.keys('default/')) # Set some preferences in the node. p.set('default/a', '1') p.set('default/b', '2') p.set('default/c', '3') keys = p.keys('default/') keys.sort() self.assertEqual(['a', 'b', 'c'], keys) # Set some preferences in a child node. p.set('default/acme.a', '1') p.set('default/acme.b', '2') p.set('default/acme.c', '3') keys = p.keys('default/acme') keys.sort() self.assertEqual(['a', 'b', 'c'], keys) # And, just to be sure, in a child of the child node ;^) p.set('default/acme.ui.a', '1') p.set('default/acme.ui.b', '2') p.set('default/acme.ui.c', '3') keys = p.keys('default/acme.ui') keys.sort() self.assertEqual(['a', 'b', 'c'], keys) return def test_node_in_specific_scope(self): """ node in specific scope """ p = self.preferences # Try an empty path. self.assertEqual(p, p.node()) # Try a simple path. node = p.node('default/acme') self.assertNotEqual(None, node) self.assertEqual('acme', node.name) self.assertEqual('acme', node.path) self.assertEqual(p.node('default/'), node.parent) # Make sure we get the same node each time we ask for it! self.assertEqual(node, p.node('default/acme')) # Try a nested path. node = p.node('default/acme.ui') self.assertNotEqual(None, node) self.assertEqual('ui', node.name) self.assertEqual('acme.ui', node.path) self.assertEqual(p.node('default/acme'), node.parent) # And just to be sure, a really nested path. node = p.node('default/acme.ui.splash_screen') self.assertNotEqual(None, node) self.assertEqual('splash_screen', node.name) self.assertEqual('acme.ui.splash_screen', node.path) self.assertEqual(p.node('default/acme.ui'), node.parent) return def test_node_exists_in_specific_scope(self): """ node exists """ p = self.preferences self.assertEqual(True, p.node_exists()) self.assertEqual(False, p.node_exists('default/acme')) p.node('default/acme') self.assertEqual(True, p.node_exists('default/acme')) return def test_node_names_in_specific_scope(self): """ node names in specific scope """ p = self.preferences # It should be empty to start with! self.assertEqual([], p.node_names('default/')) # Create some nodes. p.node('default/a') p.node('default/b') p.node('default/c') names = p.node_names('default/') names.sort() self.assertEqual(['a', 'b', 'c'], names) # Creatd some nodes in a child node. p.node('default/acme.a') p.node('default/acme.b') p.node('default/acme.c') names = p.node_names('default/acme') names.sort() self.assertEqual(['a', 'b', 'c'], names) # And, just to be sure, in a child of the child node ;^) p.node('default/acme.ui.a') p.node('default/acme.ui.b') p.node('default/acme.ui.c') names = p.node_names('default/acme.ui') names.sort() self.assertEqual(['a', 'b', 'c'], names) return def test_default_lookup_order(self): """ default lookup order """ p = self.preferences # Set a value in both the application and default scopes. p.set('application/acme.ui.bgcolor', 'red') p.set('default/acme.ui.bgcolor', 'yellow') # Make sure when we look it up we get the one in first scope in the # lookup order. self.assertEqual('red', p.get('acme.ui.bgcolor')) # But we can still get at each scope individually. self.assertEqual('red', p.get('application/acme.ui.bgcolor')) self.assertEqual('yellow', p.get('default/acme.ui.bgcolor')) return def test_lookup_order(self): """ lookup order """ p = self.preferences p.lookup_order = ['default', 'application'] # Set a value in both the application and default scopes. p.set('application/acme.ui.bgcolor', 'red') p.set('default/acme.ui.bgcolor', 'yellow') # Make sure when we look it up we get the one in first scope in the # lookup order. self.assertEqual('red', p.get('acme.ui.bgcolor')) # But we can still get at each scope individually. self.assertEqual('red', p.get('application/acme.ui.bgcolor')) self.assertEqual('yellow', p.get('default/acme.ui.bgcolor')) return def test_add_listener_in_specific_scope(self): """ add listener in specific scope. """ p = self.preferences def listener(node, key, old, new): """ Listener for changes to a preferences node. """ listener.node = node listener.key = key listener.old = old listener.new = new return # Add a listener. p.add_preferences_listener(listener, 'default/acme.ui') # Set a value and make sure the listener was called. p.set('default/acme.ui.bgcolor', 'blue') self.assertEqual(p.node('default/acme.ui'), listener.node) self.assertEqual('bgcolor', listener.key) self.assertEqual(None, listener.old) self.assertEqual('blue', listener.new) # Set it to another value to make sure we get the 'old' value # correctly. p.set('default/acme.ui.bgcolor', 'red') self.assertEqual(p.node('default/acme.ui'), listener.node) self.assertEqual('bgcolor', listener.key) self.assertEqual('blue', listener.old) self.assertEqual('red', listener.new) return def test_remove_listener_in_specific_scope(self): """ remove listener in specific scope. """ p = self.preferences def listener(node, key, old, new): """ Listener for changes to a preferences node. """ listener.node = node listener.key = key listener.old = old listener.new = new return # Add a listener. p.add_preferences_listener(listener, 'default/acme.ui') # Set a value and make sure the listener was called. p.set('default/acme.ui.bgcolor', 'blue') self.assertEqual(p.node('default/acme.ui'), listener.node) self.assertEqual('bgcolor', listener.key) self.assertEqual(None, listener.old) self.assertEqual('blue', listener.new) # Remove the listener. p.remove_preferences_listener(listener, 'default/acme.ui') # Set a value and make sure the listener was *not* called. listener.node = None p.set('default/acme.ui.bgcolor', 'blue') self.assertEqual(None, listener.node) return def test_non_existent_scope(self): """ non existent scope """ p = self.preferences self.failUnlessRaises(ValueError, p.get, 'bogus/acme.ui.bgcolor') return # Entry point for stand-alone testing. if __name__ == '__main__': unittest.main() #### EOF ###################################################################### apptools-4.1.0/apptools/preferences/tests/__init__.py0000644000175100001440000000010411674464005023755 0ustar ischnellusers00000000000000# Copyright (c) 2007-2011 by Enthought, Inc. # All rights reserved. apptools-4.1.0/apptools/preferences/tests/preferences_test_case.py0000644000175100001440000004547511674464005026575 0ustar ischnellusers00000000000000""" Tests for preferences nodes. """ # Standard library imports. import os, tempfile, unittest from os.path import join # Major package imports. from pkg_resources import resource_filename # Enthought library imports. from apptools.preferences.api import Preferences from traits.api import HasTraits, Int, Str # This module's package. PKG = 'apptools.preferences.tests' class PreferencesTestCase(unittest.TestCase): """ Tests for preferences nodes. """ ########################################################################### # 'TestCase' interface. ########################################################################### def setUp(self): """ Prepares the test fixture before each test method is called. """ self.preferences = Preferences() # The filename of the example preferences file. self.example = resource_filename(PKG, 'example.ini') # A temporary directory that can safely be written to. self.tmpdir = tempfile.mkdtemp() def tearDown(self): """ Called immediately after each test method has been called. """ # Remove the temporary directory. os.removedirs(self.tmpdir) return ########################################################################### # Tests. ########################################################################### def test_package_global_default_preferences(self): """ package global default preferences """ from apptools.preferences.api import get_default_preferences from apptools.preferences.api import set_default_preferences set_default_preferences(self.preferences) self.assertEqual(self.preferences, get_default_preferences()) return def test_get_and_set_str(self): """ get and set str """ p = self.preferences # Set a string preference. p.set('acme.ui.bgcolor', 'blue') self.assertEqual('blue', p.get('acme.ui.bgcolor')) return def test_get_and_set_int(self): """ get and set int """ p = self.preferences # Note that we can pass an actual 'int' to 'set', but the preference # manager *always* returns preference values as strings. p.set('acme.ui.width', 50) self.assertEqual('50', p.get('acme.ui.width')) return def test_get_and_set_float(self): """ get and set float """ p = self.preferences # Note that we can pass an actual 'flaot' to 'set', but the preference # manager *always* returns preference values as strings. p.set('acme.ui.ratio', 1.0) self.assertEqual('1.0', p.get('acme.ui.ratio')) return def test_get_and_set_bool(self): """ get and set bool """ p = self.preferences # Note that we can pass an actual 'bool' to 'set', but the preference # manager *always* returns preference values as strings. p.set('acme.ui.visible', True) self.assertEqual('True', p.get('acme.ui.visible')) return def test_get_and_set_list_of_str(self): """ get and set list of str """ p = self.preferences # Note that we can pass an actual 'int' to 'set', but the preference # manager *always* returns preference values as strings. p.set('acme.ui.names', ['fred', 'wilma', 'barney']) self.assertEqual("['fred', 'wilma', 'barney']", p.get('acme.ui.names')) return def test_get_and_set_list_of_int(self): """ get and set list of int """ p = self.preferences # Note that we can pass an actual 'int' to 'set', but the preference # manager *always* returns preference values as strings. p.set('acme.ui.offsets', [1, 2, 3]) self.assertEqual('[1, 2, 3]', p.get('acme.ui.offsets')) return def test_empty_path(self): """ empty path """ p = self.preferences self.failUnlessRaises(ValueError, p.get, '') self.failUnlessRaises(ValueError, p.remove, '') self.failUnlessRaises(ValueError, p.set, '', 'a value') return def test_default_values(self): """ default values """ p = self.preferences # Try non-existent names to get the default-default! self.assertEqual(None, p.get('bogus')) self.assertEqual(None, p.get('acme.bogus')) self.assertEqual(None, p.get('acme.ui.bogus')) # Try non-existent names to get the specified default. self.assertEqual('a value', p.get('bogus', 'a value')) self.assertEqual('a value', p.get('acme.bogus', 'a value')) self.assertEqual('a value', p.get('acme.ui.bogus', 'a value')) return def test_keys(self): """ keys """ p = self.preferences # It should be empty to start with! self.assertEqual([], p.keys()) # Set some preferences in the node. p.set('a', '1') p.set('b', '2') p.set('c', '3') keys = p.keys() keys.sort() self.assertEqual(['a', 'b', 'c'], keys) # Set some preferences in a child node. p.set('acme.a', '1') p.set('acme.b', '2') p.set('acme.c', '3') keys = p.keys('acme') keys.sort() self.assertEqual(['a', 'b', 'c'], keys) # And, just to be sure, in a child of the child node ;^) p.set('acme.ui.a', '1') p.set('acme.ui.b', '2') p.set('acme.ui.c', '3') keys = p.keys('acme.ui') keys.sort() self.assertEqual(['a', 'b', 'c'], keys) # Test keys of a non-existent node. self.assertEqual([], p.keys('bogus')) self.assertEqual([], p.keys('bogus.blargle')) self.assertEqual([], p.keys('bogus.blargle.foogle')) return def test_node(self): """ node """ p = self.preferences # Try an empty path. self.assertEqual(p, p.node()) # Try a simple path. node = p.node('acme') self.assertNotEqual(None, node) self.assertEqual('acme', node.name) self.assertEqual('acme', node.path) self.assertEqual(p, node.parent) # Make sure we get the same node each time we ask for it! self.assertEqual(node, p.node('acme')) # Try a nested path. node = p.node('acme.ui') self.assertNotEqual(None, node) self.assertEqual('ui', node.name) self.assertEqual('acme.ui', node.path) self.assertEqual(p.node('acme'), node.parent) # And just to be sure, a really nested path. node = p.node('acme.ui.splash_screen') self.assertNotEqual(None, node) self.assertEqual('splash_screen', node.name) self.assertEqual('acme.ui.splash_screen', node.path) self.assertEqual(p.node('acme.ui'), node.parent) return def test_node_exists(self): """ node exists """ p = self.preferences self.assertEqual(True, p.node_exists()) self.assertEqual(False, p.node_exists('acme')) p.node('acme') self.assertEqual(True, p.node_exists('acme')) return def test_node_names(self): """ node names """ p = self.preferences # It should be empty to start with! self.assertEqual([], p.node_names()) # Add some nodes. p.node('a') p.node('b') p.node('c') names = p.node_names() names.sort() self.assertEqual(['a', 'b', 'c'], names) # Creatd some nodes in a child node. p.node('acme.a') p.node('acme.b') p.node('acme.c') names = p.node_names('acme') names.sort() self.assertEqual(['a', 'b', 'c'], names) # And, just to be sure, in a child of the child node ;^) p.node('acme.ui.a') p.node('acme.ui.b') p.node('acme.ui.c') names = p.node_names('acme.ui') names.sort() self.assertEqual(['a', 'b', 'c'], names) # Test keys of a non-existent node. self.assertEqual([], p.node_names('bogus')) self.assertEqual([], p.node_names('bogus.blargle')) self.assertEqual([], p.node_names('bogus.blargle.foogle')) return def test_clear(self): """ clear """ p = self.preferences # Set some values. p.set('acme.ui.bgcolor', 'blue') self.assertEqual('blue', p.get('acme.ui.bgcolor')) p.set('acme.ui.width', 100) self.assertEqual('100', p.get('acme.ui.width')) # Clear all preferences from the node. p.clear('acme.ui') self.assertEqual(None, p.get('acme.ui.bgcolor')) self.assertEqual(None, p.get('acme.ui.width')) self.assertEqual(0, len(p.keys('acme.ui'))) return def test_remove(self): """ remove """ p = self.preferences # Set a value. p.set('acme.ui.bgcolor', 'blue') self.assertEqual('blue', p.get('acme.ui.bgcolor')) # Remove it. p.remove('acme.ui.bgcolor') self.assertEqual(None, p.get('acme.ui.bgcolor')) # Make sure we can't remove nodes! p.remove('acme.ui') self.assertEqual(True, p.node_exists('acme.ui')) return def test_flush(self): """ flush """ p = self.preferences # A temporary .ini file for this test. tmp = join(self.tmpdir, 'tmp.ini') # This could be set in the constructor of course, its just here we # want to use the instance declared in 'setUp'. p.filename = tmp try: # Load the preferences from an 'ini' file. p.load(self.example) # Flush it. p.flush() # Load it into a new node. p = Preferences() p.load(tmp) # Make sure it was all loaded! self.assertEqual('blue', p.get('acme.ui.bgcolor')) self.assertEqual('50', p.get('acme.ui.width')) self.assertEqual('1.0', p.get('acme.ui.ratio')) self.assertEqual('True', p.get('acme.ui.visible')) self.assertEqual('acme ui', p.get('acme.ui.description')) self.assertEqual('[1, 2, 3, 4]', p.get('acme.ui.offsets')) self.assertEqual("['joe', 'fred', 'jane']", p.get('acme.ui.names')) self.assertEqual('splash', p.get('acme.ui.splash_screen.image')) self.assertEqual('red', p.get('acme.ui.splash_screen.fgcolor')) finally: # Clean up! os.remove(tmp) return def test_load(self): """ load """ p = self.preferences # Load the preferences from an 'ini' file. p.load(self.example) # Make sure it was all loaded! self.assertEqual('blue', p.get('acme.ui.bgcolor')) self.assertEqual('50', p.get('acme.ui.width')) self.assertEqual('1.0', p.get('acme.ui.ratio')) self.assertEqual('True', p.get('acme.ui.visible')) self.assertEqual('acme ui', p.get('acme.ui.description')) self.assertEqual('[1, 2, 3, 4]', p.get('acme.ui.offsets')) self.assertEqual("['joe', 'fred', 'jane']", p.get('acme.ui.names')) self.assertEqual('splash', p.get('acme.ui.splash_screen.image')) self.assertEqual('red', p.get('acme.ui.splash_screen.fgcolor')) return def test_load_with_filename_trait_set(self): """ load with filename trait set """ p = self.preferences p.filename = self.example # Load the preferences from an 'ini' file. p.load() # Make sure it was all loaded! self.assertEqual('blue', p.get('acme.ui.bgcolor')) self.assertEqual('50', p.get('acme.ui.width')) self.assertEqual('1.0', p.get('acme.ui.ratio')) self.assertEqual('True', p.get('acme.ui.visible')) self.assertEqual('acme ui', p.get('acme.ui.description')) self.assertEqual('[1, 2, 3, 4]', p.get('acme.ui.offsets')) self.assertEqual("['joe', 'fred', 'jane']", p.get('acme.ui.names')) self.assertEqual('splash', p.get('acme.ui.splash_screen.image')) self.assertEqual('red', p.get('acme.ui.splash_screen.fgcolor')) p = self.preferences # Load the preferences from an 'ini' file. p.load(self.example) # Make sure it was all loaded! self.assertEqual('blue', p.get('acme.ui.bgcolor')) self.assertEqual('50', p.get('acme.ui.width')) self.assertEqual('1.0', p.get('acme.ui.ratio')) self.assertEqual('True', p.get('acme.ui.visible')) self.assertEqual('acme ui', p.get('acme.ui.description')) self.assertEqual('[1, 2, 3, 4]', p.get('acme.ui.offsets')) self.assertEqual("['joe', 'fred', 'jane']", p.get('acme.ui.names')) self.assertEqual('splash', p.get('acme.ui.splash_screen.image')) self.assertEqual('red', p.get('acme.ui.splash_screen.fgcolor')) return def test_save(self): """ save """ p = self.preferences # Load the preferences from an 'ini' file. p.load(self.example) # Make sure it was all loaded! self.assertEqual('blue', p.get('acme.ui.bgcolor')) self.assertEqual('50', p.get('acme.ui.width')) self.assertEqual('1.0', p.get('acme.ui.ratio')) self.assertEqual('True', p.get('acme.ui.visible')) self.assertEqual('acme ui', p.get('acme.ui.description')) self.assertEqual('[1, 2, 3, 4]', p.get('acme.ui.offsets')) self.assertEqual("['joe', 'fred', 'jane']", p.get('acme.ui.names')) self.assertEqual('splash', p.get('acme.ui.splash_screen.image')) self.assertEqual('red', p.get('acme.ui.splash_screen.fgcolor')) # Make a change. p.set('acme.ui.bgcolor', 'yellow') # Save it to another file. tmp = join(self.tmpdir, 'tmp.ini') p.save(tmp) try: # Load it into a new node. p = Preferences() p.load(tmp) # Make sure it was all loaded! self.assertEqual('yellow', p.get('acme.ui.bgcolor')) self.assertEqual('50', p.get('acme.ui.width')) self.assertEqual('1.0', p.get('acme.ui.ratio')) self.assertEqual('True', p.get('acme.ui.visible')) self.assertEqual('acme ui', p.get('acme.ui.description')) self.assertEqual('[1, 2, 3, 4]', p.get('acme.ui.offsets')) self.assertEqual("['joe', 'fred', 'jane']", p.get('acme.ui.names')) self.assertEqual('splash', p.get('acme.ui.splash_screen.image')) self.assertEqual('red', p.get('acme.ui.splash_screen.fgcolor')) finally: # Clean up! os.remove(tmp) return def SKIPtest_dump(self): """ dump """ # This make look like a weird test, since we don't ever actually check # anything, but it is useful for people to see the structure of a # preferences hierarchy. p = self.preferences # Load the preferences from an 'ini' file. p.load(self.example) p.dump() return def test_get_inherited(self): """ get inherited """ p = self.preferences # Set a string preference. p.set('bgcolor', 'red') p.set('acme.bgcolor', 'green') p.set('acme.ui.bgcolor', 'blue') self.assertEqual('blue', p.get('acme.ui.bgcolor', inherit=True)) # Now remove the 'lowest' layer. p.remove('acme.ui.bgcolor') self.assertEqual('green', p.get('acme.ui.bgcolor', inherit=True)) # And the next one. p.remove('acme.bgcolor') self.assertEqual('red', p.get('acme.ui.bgcolor', inherit=True)) # And the last one. p.remove('bgcolor') self.assertEqual(None, p.get('acme.ui.bgcolor', inherit=True)) return def test_add_listener(self): """ add listener """ p = self.preferences def listener(node, key, old, new): """ Listener for changes to a preferences node. """ listener.node = node listener.key = key listener.old = old listener.new = new return # Add a listener. p.add_preferences_listener(listener, 'acme.ui') # Set a value and make sure the listener was called. p.set('acme.ui.bgcolor', 'blue') self.assertEqual(p.node('acme.ui'), listener.node) self.assertEqual('bgcolor', listener.key) self.assertEqual(None, listener.old) self.assertEqual('blue', listener.new) # Set it to another value to make sure we get the 'old' value # correctly. p.set('acme.ui.bgcolor', 'red') self.assertEqual(p.node('acme.ui'), listener.node) self.assertEqual('bgcolor', listener.key) self.assertEqual('blue', listener.old) self.assertEqual('red', listener.new) return def test_remove_listener(self): """ remove listener """ p = self.preferences def listener(node, key, old, new): """ Listener for changes to a preferences node. """ listener.node = node listener.key = key listener.old = old listener.new = new return # Add a listener. p.add_preferences_listener(listener, 'acme.ui') # Set a value and make sure the listener was called. p.set('acme.ui.bgcolor', 'blue') self.assertEqual(p.node('acme.ui'), listener.node) self.assertEqual('bgcolor', listener.key) self.assertEqual(None, listener.old) self.assertEqual('blue', listener.new) # Remove the listener. p.remove_preferences_listener(listener, 'acme.ui') # Set a value and make sure the listener was *not* called. listener.node = None p.set('acme.ui.bgcolor', 'blue') self.assertEqual(None, listener.node) return def test_set_with_same_value(self): """ set with same value """ p = self.preferences def listener(node, key, old, new): """ Listener for changes to a preferences node. """ listener.node = node listener.key = key listener.old = old listener.new = new return # Add a listener. p.add_preferences_listener(listener, 'acme.ui') # Set a value and make sure the listener was called. p.set('acme.ui.bgcolor', 'blue') self.assertEqual(p.node('acme.ui'), listener.node) self.assertEqual('bgcolor', listener.key) self.assertEqual(None, listener.old) self.assertEqual('blue', listener.new) # Clear out the listener. listener.node = None # Set the same value and make sure the listener *doesn't* get called. p.set('acme.ui.bgcolor', 'blue') self.assertEqual(None, listener.node) return # Entry point for stand-alone testing. if __name__ == '__main__': unittest.main() #### EOF ###################################################################### apptools-4.1.0/apptools/preferences/tests/py_config_file.py0000644000175100001440000001752311674464005025207 0ustar ischnellusers00000000000000""" A Python based configuration file with hierarchical sections. """ class PyConfigFile(dict): """ A Python based configuration file with hierarchical sections. """ ########################################################################### # 'object' interface. ########################################################################### def __init__(self, file_or_filename=None): """ Constructor. If 'file_or_filename' is specified it will be loaded immediately. It can be either:- a) a filename b) a file-like object that must be open for reading """ # A dictionary containing one namespace instance for each root of the # config hierarchy (see the '_Namespace' class for more details). # # e.g. If the following sections have been loaded:- # # [acme.foo] # ... # [acme.bar] # ... # [tds] # ... # [tds.baz] # ... # # Then the dictionary will contain:- # # {'acme' : , 'tds' : } # self._namespaces = {} if file_or_filename is not None: self.load(file_or_filename) return ########################################################################### # 'PyConfigFile' interface. ########################################################################### def load(self, file_or_filename): """ Load the configuration from a file. 'file_or_filename' can be either:- a) a filename b) a file-like object that must be open for reading """ # Get an open file to read from. f = self._get_file(file_or_filename) section_name = None for line in f: stripped = line.strip() # Is this line a section header? # # If so then parse the preceding section (if there is one) and # start collecting the body of the new section. if stripped.startswith('[') and stripped.endswith(']'): if section_name is not None: self._parse_section(section_name, section_body) section_name = stripped[1:-1] section_body = '' # Otherwise, this is *not* a section header so add the line to the # body of the current section. If there is no current section then # we simply ignore it! else: if section_name is not None: section_body += line # Parse the last section in the file. if section_name is not None: self._parse_section(section_name, section_body) f.close() return def save(self, file_or_filename): """ Save the configuration to a file. 'file_or_filename' can be either:- a) a filename b) a file-like object that must be open for writing """ f = self._get_file(file_or_filename, 'w') for section_name, section_data in self.items(): self._write_section(f, section_name, section_data) f.close() return ########################################################################### # Private interface. ########################################################################### def _get_file(self, file_or_filename, mode='r'): """ Return an open file object from a file or a filename. The mode is only used if a filename is specified. """ if isinstance(file_or_filename, basestring): f = file(file_or_filename, mode) else: f = file_or_filename return f def _get_namespace(self, section_name): """ Return the namespace that represents the section. """ components = section_name.split('.') namespace = self._namespaces.setdefault(components[0], _Namespace()) for component in components[1:]: namespace = getattr(namespace, component) return namespace def _parse_section(self, section_name, section_body): """ Parse a section. In this implementation, we don't actually 'parse' anything - we just execute the body of the section as Python code ;^) """ # If this is the first time that we have come across the section then # start with an empty dictionary for its contents. Otherwise, we will # update its existing contents. section = self.setdefault(section_name, {}) # Execute the Python code in the section dictionary. # # We use 'self._namespaces' as the globals for the code execution so # that config values can refer to other config values using familiar # Python syntax (see the '_Namespace' class for more details). # # e.g. # # [acme.foo] # bar = 1 # baz = 99 # # [acme.blargle] # blitzel = acme.foo.bar + acme.foo.baz exec section_body in self._namespaces, section # The '__builtins__' dictionary gets added to 'self._namespaces' as # by the call to 'exec'. However, we want 'self._namespaces' to only # contain '_Namespace' instances, so we do the cleanup here. del self._namespaces['__builtins__'] # Get the section's corresponding node in the 'dotted' namespace and # update it with the config values. namespace = self._get_namespace(section_name) namespace.__dict__.update(section) return def _write_section(self, f, section_name, section_data): """ Write a section to a file. """ f.write('[%s]\n' % section_name) for name, value in section_data.items(): f.write('%s = %s\n' % (name, repr(value))) f.write('\n') return ########################################################################### # Debugging interface. ########################################################################### def _pretty_print_namespaces(self): """ Pretty print the 'dotted' namespaces. """ for name, value in self._namespaces.items(): print 'Namespace:', name value.pretty_print(' ') return ############################################################################### # Internal use only. ############################################################################### class _Namespace(object): """ An object that represents a node in a dotted namespace. We build up a dotted namespace so that config values can refer to other config values using familiar Python syntax. e.g. [acme.foo] bar = 1 baz = 99 [acme.blargle] blitzel = acme.foo.bar + acme.foo.baz """ ########################################################################### # 'object' interface. ########################################################################### def __getattr__(self, name): """ Return the attribute with the specified name. """ # This looks a little weird, but we are simply creating the next level # in the namespace hierarchy 'on-demand'. namespace = self.__dict__[name] = _Namespace() return namespace ########################################################################### # Debugging interface. ########################################################################### def pretty_print(self, indent=''): """ Pretty print the namespace. """ for name, value in self.__dict__.items(): if isinstance(value, _Namespace): print indent, 'Namespace:', name value.pretty_print(indent + ' ') else: print indent, name, ':', value return #### EOF ###################################################################### apptools-4.1.0/apptools/preferences/tests/preference_binding_test_case.py0000644000175100001440000002542611674464005030076 0ustar ischnellusers00000000000000""" Tests for preference bindings. """ # Standard library imports. import os, tempfile, unittest from os.path import join # Major package imports. from pkg_resources import resource_filename # Enthought library imports. from apptools.preferences.api import Preferences, PreferenceBinding from apptools.preferences.api import ScopedPreferences, bind_preference from apptools.preferences.api import set_default_preferences from traits.api import Bool, HasTraits, Int, Float, Str # This module's package. PKG = 'apptools.preferences.tests' def listener(obj, trait_name, old, new): """ A useful trait change handler for testing! """ listener.obj = obj listener.trait_name = trait_name listener.old = old listener.new = new return class PreferenceBindingTestCase(unittest.TestCase): """ Tests for preference bindings. """ ########################################################################### # 'TestCase' interface. ########################################################################### def setUp(self): """ Prepares the test fixture before each test method is called. """ self.preferences = set_default_preferences(Preferences()) # The filename of the example preferences file. self.example = resource_filename(PKG, 'example.ini') return def tearDown(self): """ Called immediately after each test method has been called. """ return ########################################################################### # Tests. ########################################################################### def test_preference_binding(self): """ preference binding """ p = self.preferences p.load(self.example) class AcmeUI(HasTraits): """ The Acme UI class! """ # The traits that we want to initialize from preferences. bgcolor = Str width = Int ratio = Float visible = Bool acme_ui = AcmeUI() acme_ui.on_trait_change(listener) # Make some bindings. bind_preference(acme_ui, 'bgcolor', 'acme.ui.bgcolor') bind_preference(acme_ui, 'width', 'acme.ui.width') bind_preference(acme_ui, 'ratio', 'acme.ui.ratio') bind_preference(acme_ui, 'visible', 'acme.ui.visible') # Make sure the object was initialized properly. self.assertEqual('blue', acme_ui.bgcolor) self.assertEqual(50, acme_ui.width) self.assertEqual(1.0, acme_ui.ratio) self.assertEqual(True, acme_ui.visible) # Make sure we can set the preference via the helper... acme_ui.bgcolor = 'yellow' self.assertEqual('yellow', p.get('acme.ui.bgcolor')) self.assertEqual('yellow', acme_ui.bgcolor) # ... and that the correct trait change event was fired. self.assertEqual(acme_ui, listener.obj) self.assertEqual('bgcolor', listener.trait_name) self.assertEqual('blue', listener.old) self.assertEqual('yellow', listener.new) # Make sure we can set the preference via the preferences node... p.set('acme.ui.bgcolor', 'red') self.assertEqual('red', p.get('acme.ui.bgcolor')) self.assertEqual('red', acme_ui.bgcolor) # ... and that the correct trait change event was fired. self.assertEqual(acme_ui, listener.obj) self.assertEqual('bgcolor', listener.trait_name) self.assertEqual('yellow', listener.old) self.assertEqual('red', listener.new) # Make sure we can set a non-string preference via the helper... acme_ui.ratio = 0.5 self.assertEqual('0.5', p.get('acme.ui.ratio')) self.assertEqual(0.5, acme_ui.ratio) # Make sure we can set a non-string preference via the node... p.set('acme.ui.ratio', '0.75') self.assertEqual('0.75', p.get('acme.ui.ratio')) self.assertEqual(0.75, acme_ui.ratio) return def test_default_values(self): """ instance scope preferences path """ p = self.preferences class AcmeUI(HasTraits): """ The Acme UI class! """ # The traits that we want to initialize from preferences. bgcolor = Str('blue') width = Int(50) ratio = Float(1.0) visible = Bool(True) acme_ui = AcmeUI() # Make some bindings. bind_preference(acme_ui, 'bgcolor', 'acme.ui.bgcolor') bind_preference(acme_ui, 'width', 'acme.ui.width') bind_preference(acme_ui, 'ratio', 'acme.ui.ratio') bind_preference(acme_ui, 'visible', 'acme.ui.visible') # Make sure the helper was initialized properly. self.assertEqual('blue', acme_ui.bgcolor) self.assertEqual(50, acme_ui.width) self.assertEqual(1.0, acme_ui.ratio) self.assertEqual(True, acme_ui.visible) return def test_load_and_save(self): """ load and save """ p = self.preferences p.load(self.example) class AcmeUI(HasTraits): """ The Acme UI class! """ # The traits that we want to initialize from preferences. bgcolor = Str('red') width = Int(60) ratio = Float(2.0) visible = Bool(False) acme_ui = AcmeUI() # Make some bindings. bind_preference(acme_ui, 'bgcolor', 'acme.ui.bgcolor') bind_preference(acme_ui, 'width', 'acme.ui.width') bind_preference(acme_ui, 'ratio', 'acme.ui.ratio') bind_preference(acme_ui, 'visible', 'acme.ui.visible') # Make sure the helper was initialized properly (with the values in # the loaded .ini file *not* the trait defaults!). self.assertEqual('blue', acme_ui.bgcolor) self.assertEqual(50, acme_ui.width) self.assertEqual(1.0, acme_ui.ratio) self.assertEqual(True, acme_ui.visible) # Make a change to one of the preference values. p.set('acme.ui.bgcolor', 'yellow') self.assertEqual('yellow', acme_ui.bgcolor) self.assertEqual('yellow', p.get('acme.ui.bgcolor')) # Save the preferences to a different file. tmpdir = tempfile.mkdtemp() tmp = join(tmpdir, 'tmp.ini') p.save(tmp) # Load the preferences again from that file. p = set_default_preferences(Preferences()) p.load(tmp) acme_ui = AcmeUI() # Make some bindings. bind_preference(acme_ui, 'bgcolor', 'acme.ui.bgcolor') bind_preference(acme_ui, 'width', 'acme.ui.width') bind_preference(acme_ui, 'ratio', 'acme.ui.ratio') bind_preference(acme_ui, 'visible', 'acme.ui.visible') # Make sure the helper was initialized properly (with the values in # the .ini file *not* the trait defaults!). self.assertEqual('yellow', acme_ui.bgcolor) self.assertEqual(50, acme_ui.width) self.assertEqual(1.0, acme_ui.ratio) self.assertEqual(True, acme_ui.visible) # Clean up! os.remove(tmp) os.removedirs(tmpdir) return def test_explicit_preferences(self): """ explicit preferences """ p = self.preferences p.load(self.example) class AcmeUI(HasTraits): """ The Acme UI class! """ # The traits that we want to initialize from preferences. bgcolor = Str width = Int ratio = Float visible = Bool acme_ui = AcmeUI() acme_ui.on_trait_change(listener) # Create an empty preferences node and use that in some of the # bindings! preferences = Preferences() # Make some bindings. bind_preference(acme_ui, 'bgcolor', 'acme.ui.bgcolor', preferences) bind_preference(acme_ui, 'width', 'acme.ui.width') bind_preference(acme_ui, 'ratio', 'acme.ui.ratio', preferences) bind_preference(acme_ui, 'visible', 'acme.ui.visible') # Make sure the object was initialized properly. self.assertEqual('', acme_ui.bgcolor) self.assertEqual(50, acme_ui.width) self.assertEqual(0.0, acme_ui.ratio) self.assertEqual(True, acme_ui.visible) return def test_nested_set_in_trait_change_handler(self): """ nested set in trait change handler """ p = self.preferences p.load(self.example) class AcmeUI(HasTraits): """ The Acme UI class! """ # The traits that we want to initialize from preferences. bgcolor = Str width = Int ratio = Float visible = Bool def _width_changed(self, trait_name, old, new): """ Static trait change handler. """ self.ratio = 3.0 return acme_ui = AcmeUI() acme_ui.on_trait_change(listener) # Make some bindings. bind_preference(acme_ui, 'bgcolor', 'acme.ui.bgcolor') bind_preference(acme_ui, 'width', 'acme.ui.width') bind_preference(acme_ui, 'ratio', 'acme.ui.ratio') bind_preference(acme_ui, 'visible', 'acme.ui.visible') # Make sure the object was initialized properly. self.assertEqual('blue', acme_ui.bgcolor) self.assertEqual(50, acme_ui.width) self.assertEqual(1.0, acme_ui.ratio) self.assertEqual(True, acme_ui.visible) # Change the width via the preferences node. This should cause the # ratio to get set via the static trait change handler on the helper. p.set('acme.ui.width', 42) self.assertEqual(42, acme_ui.width) self.assertEqual('42', p.get('acme.ui.width')) # Did the ratio get changed? self.assertEqual(3.0, acme_ui.ratio) self.assertEqual('3.0', p.get('acme.ui.ratio')) return def test_trait_name_different_to_preference_name(self): p = self.preferences p.load(self.example) class AcmeUI(HasTraits): """ The Acme UI class! """ # The test here is to have a different name for the trait than the # preference value (which is 'bgcolor'). color = Str acme_ui = AcmeUI() acme_ui.on_trait_change(listener) # Make some bindings. bind_preference(acme_ui, 'color', 'acme.ui.bgcolor') # Make sure the object was initialized properly. self.assertEqual('blue', acme_ui.color) # Change the width via the preferences node. p.set('acme.ui.bgcolor', 'red') self.assertEqual('color', listener.trait_name) self.assertEqual('blue', listener.old) self.assertEqual('red', listener.new) return # Entry point for stand-alone testing. if __name__ == '__main__': unittest.main() #### EOF ###################################################################### apptools-4.1.0/apptools/preferences/ui/0000755000175100001440000000000011674464005021124 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/preferences/ui/api.py0000644000175100001440000000022111674464005022242 0ustar ischnellusers00000000000000from i_preferences_page import IPreferencesPage from preferences_manager import PreferencesManager from preferences_page import PreferencesPage apptools-4.1.0/apptools/preferences/ui/tree_item.py0000644000175100001440000001010111674464005023444 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A generic base-class for items in a tree data structure. An example:- root = TreeItem(data='Root') fruit = TreeItem(data='Fruit') fruit.append(TreeItem(data='Apple', allows_children=False)) fruit.append(TreeItem(data='Orange', allows_children=False)) fruit.append(TreeItem(data='Pear', allows_children=False)) root.append(fruit) veg = TreeItem(data='Veg') veg.append(TreeItem(data='Carrot', allows_children=False)) veg.append(TreeItem(data='Cauliflower', allows_children=False)) veg.append(TreeItem(data='Sprout', allows_children=False)) root.append(veg) """ # Enthought library imports. from traits.api import Any, Bool, HasTraits, Instance, List, Property class TreeItem(HasTraits): """ A generic base-class for items in a tree data structure. """ #### 'TreeItem' interface ################################################# # Does this item allow children? allows_children = Bool(True) # The item's children. children = List(Instance('TreeItem')) # Arbitrary data associated with the item. data = Any # Does the item have any children? has_children = Property(Bool) # The item's parent. parent = Instance('TreeItem') ########################################################################### # 'object' interface. ########################################################################### def __str__(self): """ Returns the informal string representation of the object. """ if self.data is None: s = '' else: s = str(self.data) return s ########################################################################### # 'TreeItem' interface. ########################################################################### #### Properties ########################################################### # has_children def _get_has_children(self): """ True iff the item has children. """ return len(self.children) != 0 #### Methods ############################################################## def append(self, child): """ Appends a child to this item. This removes the child from its current parent (if it has one). """ return self.insert(len(self.children), child) def insert(self, index, child): """ Inserts a child into this item at the specified index. This removes the child from its current parent (if it has one). """ if child.parent is not None: child.parent.remove(child) child.parent = self self.children.insert(index, child) return child def remove(self, child): """ Removes a child from this item. """ child.parent = None self.children.remove(child) return child def insert_before(self, before, child): """ Inserts a child into this item before the specified item. This removes the child from its current parent (if it has one). """ index = self.children.index(before) self.insert(index, child) return (index, child) def insert_after(self, after, child): """ Inserts a child into this item after the specified item. This removes the child from its current parent (if it has one). """ index = self.children.index(after) self.insert(index + 1, child) return (index, child) #### EOF ###################################################################### apptools-4.1.0/apptools/preferences/ui/i_preferences_page.py0000644000175100001440000000227611674464005025312 0ustar ischnellusers00000000000000""" The interface for pages in a preferences dialog. """ # Enthought library imports. from traits.api import Interface, Str class IPreferencesPage(Interface): """ The interface for pages in a preferences dialog. """ # The page's category (e.g. 'General/Appearence'). The empty string means # that this is a top-level page. category = Str # The page's help identifier (optional). If a help Id *is* provided then # there will be a 'Help' button shown on the preference page. help_id = Str # The page name (this is what is shown in the preferences dialog). name = Str def apply(self): """ Apply the page's preferences. """ # fixme: We would like to be able to have the following API so that # developers are not forced into using traits UI for their preferences # pages, but at the moment I can't work out how to do it! ## def create_control(self, parent): ## """ Create the toolkit-specific control that represents the page. """ ## def destroy_control(self, parent): ## """ Destroy the toolkit-specific control that represents the page. """ #### EOF ###################################################################### apptools-4.1.0/apptools/preferences/ui/preferences_node.py0000644000175100001440000000604511674464005025011 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Abstract base class for a node in a preferences dialog. """ # Enthought library imports. from traits.api import Delegate, Instance, Str # Local imports. from i_preferences_page import IPreferencesPage from tree_item import TreeItem class PreferencesNode(TreeItem): """ Abstract base class for a node in a preferences dialog. A preferences node has a name and an image which are used to represent the node in a preferences dialog (usually in the form of a tree). """ #### 'PreferenceNode' interface ########################################### # The page's help identifier (optional). If a help Id *is* provided then # there will be a 'Help' button shown on the preference page. help_id = Delegate('page') # The page name (this is what is shown in the preferences dialog. name = Delegate('page') # The page that we are a node for. page = Instance(IPreferencesPage) ########################################################################### # 'object' interface. ########################################################################### def __str__(self): """ Returns the string representation of the item. """ if self.page is None: s = 'root' else: s = self.page.name return s __repr__ = __str__ ########################################################################### # 'PreferencesNode' interface. ########################################################################### def create_page(self, parent): """ Creates the preference page for this node. """ return self.page.create_control(parent) def lookup(self, name): """ Returns the child of this node with the specified Id. Returns None if no such child exists. """ for node in self.children: if node.name == name: break else: node = None return node ########################################################################### # Debugging interface. ########################################################################### def dump(self, indent=''): """ Pretty-print the node to stdout. """ print indent, 'Node', str(self) for child in self.children: child.dump(indent+' ') return #### EOF ###################################################################### apptools-4.1.0/apptools/preferences/ui/preferences_manager.py0000644000175100001440000002274411674464005025502 0ustar ischnellusers00000000000000""" The preferences manager. """ # Enthought library imports. from traits.api import HasTraits, Instance, List, Property, \ Any, Bool, Dict from traitsui.api import Handler, HSplit, Item, TreeEditor from traitsui.api import TreeNode, View, HTMLEditor from traitsui.menu import Action # Local imports. from preferences_node import PreferencesNode from preferences_page import PreferencesPage # fixme: This is part of the attempt to allow developers to use non-Traits UI # preferences pages. It doesn't work yet! ##from widget_editor import WidgetEditor # A tree editor for preferences nodes. tree_editor = TreeEditor( nodes = [ TreeNode( node_for = [PreferencesNode], auto_open = False, children = 'children', label = 'name', rename = False, copy = False, delete = False, insert = False, menu = None, ), ], editable = False, hide_root = True, selected = 'selected_node', show_icons = False ) class PreferencesHelpWindow(HasTraits): """ Container class to present a view with string info. """ def traits_view(self): """ Default view to show for this class. """ args = [] kw_args = {'title' : 'Preferences Page Help', 'buttons' : ['OK'], 'width' : 800, 'height' : 800, 'resizable' : True, 'id' : 'apptools.preferences.ui.preferences_manager.help'} to_show = {} for name, trait_obj in self.traits().items(): if name != 'trait_added' and name != 'trait_modified': to_show[name] = trait_obj.help for name in to_show: args.append(Item(name, style='readonly', editor=HTMLEditor() )) view = View(*args, **kw_args) return view class PreferencesManagerHandler(Handler): """ The traits UI handler for the preferences manager. """ model = Instance(HasTraits) ########################################################################### # 'Handler' interface. ########################################################################### def apply(self, info): """ Handle the **Apply** button being clicked. """ info.object.apply() return def init(self, info): """ Initialize the controls of a user interface. """ # Select the first node in the tree (if there is one). self._select_first_node(info) return super(PreferencesManagerHandler, self).init(info) def close(self, info, is_ok): """ Close a dialog-based user interface. """ if is_ok: info.object.apply() return super(PreferencesManagerHandler, self).close(info, is_ok) def preferences_help(self, info): """ Custom preferences help panel. The Traits help doesn't work.""" current_page = self.model.selected_page to_show = {} for trait_name, trait_obj in current_page.traits().items(): if hasattr(trait_obj, 'show_help') and trait_obj.show_help: to_show[trait_name] = trait_obj.help help_obj = PreferencesHelpWindow(**to_show) help_obj.edit_traits(kind='livemodal') return ########################################################################### # Private interface. ########################################################################### def _select_first_node(self, info): """ Select the first node in the tree (if there is one). """ root = info.object.root if len(root.children) > 0: node = root.children[0] info.object.selected_page = node.page return class PreferencesManager(HasTraits): """ The preferences manager. """ # All of the preferences pages known to the manager. pages = List(PreferencesPage) # The root of the preferences node tree. root = Property(Instance(PreferencesNode)) # The preferences node currently selected in the tree. selected_node = Instance(PreferencesNode) # The preferences associated with the currently selected preferences node. selected_page = Instance(PreferencesPage) # Should the custom Info button be shown? If this is True, then an # Info button is shown that pops up a trait view with an HTML entry # for each trait of the *selected_page* with the metadata 'show_help' # set to True. show_help = Bool(False) # Should the Apply button be shown? show_apply = Bool(False) #### Traits UI views ###################################################### def traits_view(self): """ Default traits view for this class. """ help_action = Action(name = 'Info', action = 'preferences_help') buttons = ['OK', 'Cancel'] if self.show_apply: buttons = ['Apply'] + buttons if self.show_help: buttons = [help_action] + buttons # A tree editor for preferences nodes. tree_editor = TreeEditor( nodes = [ TreeNode( node_for = [PreferencesNode], auto_open = False, children = 'children', label = 'name', rename = False, copy = False, delete = False, insert = False, menu = None, ), ], on_select = self._selection_changed, editable = False, hide_root = True, selected = 'selected_node', show_icons = False ) view = View( HSplit( Item( name = 'root', editor = tree_editor, show_label = False, width = 250, ), Item( name = 'selected_page', #editor = WidgetEditor(), show_label = False, width = 450, style = 'custom', ), ), buttons = buttons, handler = PreferencesManagerHandler(model=self), resizable = True, title = 'Preferences', width = .3, height = .3, kind = 'modal' ) self.selected_page = self.pages[0] return view ########################################################################### # 'PreferencesManager' interface. ########################################################################### #### Trait properties ##################################################### def _get_root(self): """ Property getter. """ # Sort the pages by the length of their category path. This makes it # easy for us to create the preference hierarchy as we know that all of # a node's ancestors will have already been created. def sort(a, b): # We have the guard because if the category is the empty string # then split will still return a list containing one item (and not # the empty list). if len(a.category) == 0: len_a = 0 else: len_a = len(a.category.split('/')) if len(b.category) == 0: len_b = 0 else: len_b = len(b.category.split('/')) return cmp(len_a, len_b) self.pages.sort(sort) # Create a corresponding preference node hierarchy (the root of the # hierachy is NOT displayed in the preference dialog). # # fixme: Currently we have to create a dummy page for the root node # event though the root does not get shown in the tree! root_page = PreferencesPage(name='Root', preferences_path='root') root = PreferencesNode(page=root_page) for page in self.pages: # Get the page's parent node. parent = self._get_parent(root, page) # Add a child node representing the page. parent.append(PreferencesNode(page=page)) return root #### Trait change handlers ################################################ def _selection_changed(self, new_selection): self.selected_node = new_selection def _selected_node_changed(self, new): """ Static trait change handler. """ if self.selected_node: self.selected_page = self.selected_node.page return #### Methods ############################################################## def apply(self): """ Apply all changes made in the manager. """ for page in self.pages: page.apply() return ########################################################################### # Private interface. ########################################################################### def _get_parent(self, root, page): """ Return the page's parent preference node. """ parent = root if len(page.category) > 0: components = page.category.split('/') for component in components: parent = parent.lookup(component) return parent #### EOF ###################################################################### apptools-4.1.0/apptools/preferences/ui/preferences_page.py0000644000175100001440000001016311674464005024774 0ustar ischnellusers00000000000000""" A page in a preferences dialog. """ # Enthought library imports. from apptools.preferences.api import PreferencesHelper from traits.api import Any, Dict, Str, implements # Local imports. from i_preferences_page import IPreferencesPage class PreferencesPage(PreferencesHelper): """ A page in a preferences dialog. """ implements(IPreferencesPage) #### 'IPreferencesPage' interface ######################################### # The page's category (e.g. 'General/Appearance'). The empty string means # that this is a top-level page. category = Str # DEPRECATED: The help_id was never fully implemented, and it's been # over two years (now 4/2009). The original goal was for the the Help # button to automatically appear and connect to a help page with a # help_id. Not removing the trait right now to avoid breaking code # that may be checking for this. # # Use PreferencesManager.show_help and trait show_help metadata instead. help_id = Str # The page name (this is what is shown in the preferences dialog. name = Str #### Private interface #################################################### # The traits UI that represents the page. _ui = Any # A dictionary containing the traits that have been changed since the # last call to 'apply'. _changed = Dict ########################################################################### # 'IPreferencesPage' interface. ########################################################################### def apply(self): """ Apply the page's preferences. """ path = self._get_path() for trait_name, value in self._changed.items(): if self._is_preference_trait(trait_name): self.preferences.set('%s.%s' % (path, trait_name), value) self._changed.clear() return # fixme: We would like to be able to have the following API so that # developers are not forced into using traits UI for their preferences # pages, but at the moment I can't work out how to do it! ## def create_control(self, parent): ## """ Create the toolkit-specific control that represents the page. """ ## if self._ui is None: ## self._ui = self.edit_traits(parent=parent, kind='subpanel') ## return self._ui.control ## def destroy_control(self): ## """ Destroy the toolkit-specific control that represents the page. """ ## if self._ui is not None: ## self._ui.dispose() ## self._ui = None ## return ########################################################################### # Private interface. ########################################################################### #### Trait change handlers ################################################ def _anytrait_changed(self, trait_name, old, new): """ Static trait change handler. This is an important override! In the base-class when a trait is changed the preferences node is updated too. Here, we stop that from happening and just make a note of what changes have been made. The preferences node gets updated when the 'apply' method is called. """ # If the trait was a list or dict '_items' trait then just treat it as # if the entire list or dict was changed. if trait_name.endswith('_items'): trait_name = trait_name[:-6] if self._is_preference_trait(trait_name): self._changed[trait_name] = getattr(self, trait_name) elif self._is_preference_trait(trait_name): self._changed[trait_name] = new return # fixme: Pretty much duplicated in 'PreferencesHelper' (except for the # class name of course!). def _is_preference_trait(self, trait_name): """ Return True if a trait represents a preference value. """ if trait_name.startswith('_') or trait_name.endswith('_') \ or trait_name in PreferencesPage.class_traits(): return False return True #### EOF ###################################################################### apptools-4.1.0/apptools/preferences/ui/__init__.py0000644000175100001440000000010411674464005023230 0ustar ischnellusers00000000000000# Copyright (c) 2007-2011 by Enthought, Inc. # All rights reserved. apptools-4.1.0/apptools/preferences/ui/widget_editor.py0000644000175100001440000000572511674464005024340 0ustar ischnellusers00000000000000""" An instance editor that allows total control over widget creation. """ # Enthought library imports. from traits.etsconfig.api import ETSConfig from traits.api import Any from traitsui.api import EditorFactory # fixme: We need to import the 'Editor' class from the appropriate toolkit. exec('from traitsui.%s.editor import Editor' % ETSConfig.toolkit) class _WidgetEditor(Editor): """ An instance editor that allows total control over widget creation. """ #### '_WidgetEditor' interface ############################################ # The toolkit-specific parent of the editor. parent = Any ########################################################################### # '_WidgetEditor' interface. ########################################################################### def init(self, parent): """ Initialize the editor. """ self.parent = parent # fixme: What if there are no pages?!? page = self.object.pages[0] # Create the editor's control. self.control = page.create_control(parent) # Listen for the page being changed. self.object.on_trait_change(self._on_page_changed, 'selected_page') return def dispose(self): """ Dispose of the editor. """ page = self.object.selected_page page.destroy_control() return def update_editor(self): """ Update the editor. """ pass ########################################################################### # Private interface. ########################################################################### def _on_page_changed(self, obj, trait_name, old, new): """ Dynamic trait change handler. """ if old is not None: old.destroy_control() if new is not None: self.control = new.create_control(self.parent) return class WidgetEditor(EditorFactory): """ A factory widget editors. """ ########################################################################### # 'object' interface. ########################################################################### def __call__ (self, *args, **traits): """ Call the object. """ return self.set(**traits) ########################################################################### # 'EditorFactory' interface. ########################################################################### def simple_editor(self, ui, object, name, description, parent): """ Create a simple editor. """ editor = _WidgetEditor( parent, factory = self, ui = ui, object = object, name = name, description = description ) return editor custom_editor = simple_editor text_editor = simple_editor readonly_editor = simple_editor #### EOF ###################################################################### apptools-4.1.0/apptools/preferences/preference_binding.py0000644000175100001440000001341311674464005024673 0ustar ischnellusers00000000000000""" A binding between a trait on an object and a preference value. """ # Enthought library imports. from traits.api import Any, HasTraits, Instance, Str, Undefined from traits.api import Unicode # Local imports. from i_preferences import IPreferences from package_globals import get_default_preferences class PreferenceBinding(HasTraits): """ A binding between a trait on an object and a preference value. """ #### 'PreferenceBinding' interface ######################################## # The object that we are binding the preference to. obj = Any # The preferences node used by the binding. If this trait is not set then # the package-global default preferences node is used (and if that is not # set then the binding won't work ;^) preferences = Instance(IPreferences) # The path to the preference value. preference_path = Str # The name of the trait that we are binding the preference to. trait_name = Str ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **traits): """ Constructor. """ super(PreferenceBinding, self).__init__(**traits) # Initialize the object's trait from the preference value. self._set_trait(notify=False) # Wire-up trait change handlers etc. self._initialize() return ########################################################################### # 'PreferenceBinding' interface. ########################################################################### #### Trait initializers ################################################### def _preferences_default(self): """ Trait initializer. """ return get_default_preferences() ########################################################################### # Private interface. ########################################################################### #### Trait change handlers ################################################ def _on_trait_changed(self, obj, trait_name, old, new): """ Dynamic trait change handler. """ self.preferences.set(self.preference_path, new) return #### Other observer pattern listeners ##################################### def _preferences_listener(self, node, key, old, new): """ Listener called when a preference value is changed. """ components = self.preference_path.split('.') if key == components[-1]: self._set_trait() return #### Methods ############################################################## # fixme: This method is mostly duplicated in 'PreferencesHelper' (the only # difference is the line that gets the handler). def _get_value(self, trait_name, value): """ Get the actual value to set. This method makes sure that any required work is done to convert the preference value from a string. """ handler = self.obj.trait(trait_name).handler # If the trait type is 'Str' then we just take the raw value. if type(handler) is Str: pass # If the trait type is 'Unicode' then we convert the raw value. elif type(handler) is Unicode: value = unicode(value) # Otherwise, we eval it! else: try: value = eval(value) # If the eval fails then there is probably a syntax error, but # we will let the handler validation throw the exception. except: pass return handler.validate(self, trait_name, value) def _initialize(self): """ Wire-up trait change handlers etc. """ # Listen for the object's trait being changed. self.obj.on_trait_change(self._on_trait_changed, self.trait_name) # Listen for the preference value being changed. components = self.preference_path.split('.') node = '.'.join(components[:-1]) self.preferences.add_preferences_listener( self._preferences_listener, node ) return def _set_trait(self, notify=True): """ Set the object's trait to the value of the preference. """ value = self.preferences.get(self.preference_path, Undefined) if value is not Undefined: trait_value = self._get_value(self.trait_name, value) traits = {self.trait_name : trait_value} self.obj.set(trait_change_notify=notify, **traits) return # Factory function for creating bindings. def bind_preference(obj, trait_name, preference_path, preferences=None): """ Create a new preference binding. """ # This may seem a bit wierd, but we manually build up a dictionary of # the traits that need to be set at the time the 'PreferenceBinding' # instance is created. # # This is because we only want to set the 'preferences' trait iff one # is explicitly specified. If we passed it in with the default argument # value of 'None' then it counts as 'setting' the trait which prevents # the binding instance from defaulting to the package-global preferences. # Also, if we try to set the 'preferences' trait *after* construction time # then it is too late as the binding initialization is done in the # constructor (we could of course split that out, which may be the 'right' # way to do it ;^). traits = { 'obj' : obj, 'trait_name' : trait_name, 'preference_path' : preference_path } if preferences is not None: traits['preferences'] = preferences return PreferenceBinding(**traits) #### EOF ###################################################################### apptools-4.1.0/apptools/preferences/api.py0000644000175100001440000000047511674464005021640 0ustar ischnellusers00000000000000from i_preferences import IPreferences from package_globals import get_default_preferences, set_default_preferences from preferences import Preferences from preference_binding import PreferenceBinding, bind_preference from preferences_helper import PreferencesHelper from scoped_preferences import ScopedPreferences apptools-4.1.0/apptools/preferences/preferences_helper.py0000644000175100001440000001410011674464005024715 0ustar ischnellusers00000000000000""" An object that can be initialized from a preferences node. """ # Standard library imports. import logging # Enthought library imports. from traits.api import HasTraits, Instance, Str, Unicode # Local imports. from i_preferences import IPreferences from package_globals import get_default_preferences # Logging. logger = logging.getLogger(__name__) class PreferencesHelper(HasTraits): """ An object that can be initialized from a preferences node. """ #### 'PreferencesHelper' interface ######################################## # The preferences node used by the helper. If this trait is not set then # the package-global default preferences node is used. # # fixme: This introduces a 'sneaky' global reference to the preferences # node! preferences = Instance(IPreferences) # The path to the preference node that contains the preferences that we # use to initialize instances of this class. preferences_path = Str ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **traits): """ Constructor. """ super(PreferencesHelper, self).__init__(**traits) # Initialize the object's traits from the preferences node. if self.preferences: self._initialize(self.preferences) return ########################################################################### # Private interface. ########################################################################### #### Trait initializers ################################################### def _preferences_default(self): """ Trait initializer. """ # If no specific preferences node is set then we use the package-wide # global node. return get_default_preferences() #### Trait change handlers ################################################ def _anytrait_changed(self, trait_name, old, new): """ Static trait change handler. """ # If we were the one that set the trait (because the underlying # preferences node changed) then do nothing. if self.preferences and self._is_preference_trait(trait_name): self.preferences.set('%s.%s' % (self._get_path(), trait_name), new) return def _preferences_changed(self, old, new): """ Static trait change handler. """ # Stop listening to the old preferences node. if old is not None: old.remove_preferences_listener( self._preferences_changed_listener, self._get_path() ) if new is not None: # Initialize with the new preferences node (this also adds a # listener for preferences being changed in the new node). self._initialize(new, notify=True) return #### Other observer pattern listeners ##################################### def _preferences_changed_listener(self, node, key, old, new): """ Listener called when a preference value is changed. """ if key in self.trait_names(): setattr(self, key, self._get_value(key, new)) return #### Methods ############################################################## def _get_path(self): """ Return the path to our preferences node. """ if len(self.preferences_path) > 0: path = self.preferences_path else: path = getattr(self, 'PREFERENCES_PATH', None) if path is None: raise SystemError('no preferences path' % self) else: logger.warn('DEPRECATED: use "preferences_path" %s' % self) return path def _get_value(self, trait_name, value): """ Get the actual value to set. This method makes sure that any required work is done to convert the preference value from a string. Str traits or those with the metadata 'is_str=True' will just be passed the string itself. """ trait = self.trait(trait_name) handler = trait.handler # If the trait type is 'Str' then we just take the raw value. if isinstance(handler, Str) or trait.is_str: pass # If the trait type is 'Unicode' then we convert the raw value. elif isinstance(handler, Unicode): value = unicode(value) # Otherwise, we eval it! else: try: value = eval(value) # If the eval fails then there is probably a syntax error, but # we will let the handler validation throw the exception. except: pass if handler.validate is not None: # Any traits have a validator of None. validated = handler.validate(self, trait_name, value) else: validated = value return validated def _initialize(self, preferences, notify=False): """ Initialize the object's traits from the preferences node. """ path = self._get_path() keys = preferences.keys(path) traits_to_set = {} for trait_name in self.trait_names(): if trait_name in keys: key = '%s.%s' % (path, trait_name) value = self._get_value(trait_name, preferences.get(key)) traits_to_set[trait_name] = value self.set(trait_change_notify=notify, **traits_to_set) # Listen for changes to the node's preferences. preferences.add_preferences_listener( self._preferences_changed_listener, path ) return # fixme: Pretty much duplicated in 'PreferencesPage' (except for the # class name of course!). def _is_preference_trait(self, trait_name): """ Return True if a trait represents a preference value. """ if trait_name.startswith('_') or trait_name.endswith('_') \ or trait_name in PreferencesHelper.class_traits(): return False return True #### EOF ###################################################################### apptools-4.1.0/apptools/preferences/scoped_preferences.py0000644000175100001440000003412311674464005024722 0ustar ischnellusers00000000000000""" A preferences node that adds the notion of preferences scopes. """ # Standard library imports. from os.path import join # Enthought library imports. from traits.etsconfig.api import ETSConfig from traits.api import List, Str, Undefined # Local imports. from i_preferences import IPreferences from preferences import Preferences class ScopedPreferences(Preferences): """ A preferences node that adds the notion of preferences scopes. Scopes provide a way to access preferences in a precedence order, usually depending on where they came from (see 'ApplicationPreferences' for a common usage pattern). By default, this class provides two scopes - 'application' which is persistent and 'default' which is not. However, as mentioned above, I would suggest that the newer 'ApplicationPreferences' class is a more useful starting point). Path names passed to 'ScopedPreferences' nodes can be either:: a) a preference path as used in a standard 'Preferences' node, e.g:: 'acme.widget.bgcolor'. In this case the operation either takes place in the primary scope (for operations such as 'set' etc), or on all scopes in precedence order (for operations such as 'get' etc). or b) a preference path that refers to a specific scope e.g:: 'default/acme.widget.bgcolor' In this case the operation takes place *only* in the specified scope. There is one drawback to this scheme. If you want to access a scope node itself via the 'clear', 'keys', 'node', 'node_exists' or 'node_names' methods then you have to append a trailing '/' to the path. Without that, the node would try to perform the operation in the primary scope. e.g. To get the names of the children of the 'application' scope, use:: scoped.node_names('application/') If you did this:: scoped.node_names('application') Then the node would get the primary scope and try to find its child node called 'application'. Of course you can just get the scope via:: application_scope = scoped.get_scope('application') and then call whatever methods you like on it - which is definitely more intentional and is highly recommended:: application_scope.node_names() """ #### 'ScopedPreferences' interface ######################################## # The file that the application scope preferences are stored in. # # Defaults to:- # # os.path.join(ETSConfig.application_home, 'preferences.ini') application_preferences_filename = Str # The scopes (in the order that they should be searched when looking up # preferences). # # By default, this class provides two scopes - 'application' which is # persistent and 'default' which is not. scopes = List(IPreferences) # The name of the 'primary' scope. # # This is the scope that operations take place in if no scope is specified # in a given path (for the 'get' operation, if no scope is specified the # operation takes place in *all* scopes in order of precedence). If this is # the empty string (the default) then the primary scope is the first scope # in the 'scopes' list. primary_scope_name = Str ########################################################################### # 'IPreferences' protocol. ########################################################################### #### Methods where 'path' refers to a preference #### def get(self, path, default=None, inherit=False): """ Get the value of the preference at the specified path. """ if len(path) == 0: raise ValueError('empty path') # If the path contains a specific scope then lookup the preference in # just that scope. if self._path_contains_scope(path): scope_name, path = self._parse_path(path) nodes = [self._get_scope(scope_name)] # Otherwise, try each scope in turn (i.e. in order of precedence). else: nodes = self.scopes # Try all nodes first (without inheritance even if specified). value = self._get(path, Undefined, nodes, inherit=False) if value is Undefined: if inherit: value = self._get(path, default, nodes, inherit=True) else: value = default return value def remove(self, path): """ Remove the preference at the specified path. """ if len(path) == 0: raise ValueError('empty path') # If the path contains a specific scope then remove the preference from # just that scope. if self._path_contains_scope(path): scope_name, path = self._parse_path(path) node = self._get_scope(scope_name) # Otherwise, remove the preference from the primary scope. else: node = self._get_primary_scope() node.remove(path) return def set(self, path, value): """ Set the value of the preference at the specified path. """ if len(path) == 0: raise ValueError('empty path') # If the path contains a specific scope then set the value in that # scope. if self._path_contains_scope(path): scope_name, path = self._parse_path(path) node = self._get_scope(scope_name) # Otherwise, set the value in the primary scope. else: node = self._get_primary_scope() node.set(path, value) return #### Methods where 'path' refers to a node #### def clear(self, path=''): """ Remove all preference from the node at the specified path. """ # If the path contains a specific scope then remove the preferences # from a node in that scope. if self._path_contains_scope(path): scope_name, path = self._parse_path(path) node = self._get_scope(scope_name) # Otherwise, remove the preferences from a node in the primary scope. else: node = self._get_primary_scope() return node.clear(path) def keys(self, path=''): """ Return the preference keys of the node at the specified path. """ # If the path contains a specific scope then get the keys of the node # in that scope. if self._path_contains_scope(path): scope_name, path = self._parse_path(path) nodes = [self._get_scope(scope_name)] # Otherwise, merge the keys of the node in all scopes. else: nodes = self.scopes keys = set() for node in nodes: keys.update(node.node(path).keys()) return list(keys) def node(self, path=''): """ Return the node at the specified path. """ if len(path) == 0: node = self else: # If the path contains a specific scope then we get the node that # scope. if self._path_contains_scope(path): scope_name, path = self._parse_path(path) node = self._get_scope(scope_name) # Otherwise, get the node from the primary scope. else: node = self._get_primary_scope() node = node.node(path) return node def node_exists(self, path=''): """ Return True if the node at the specified path exists. """ # If the path contains a specific scope then look for the node in that # scope. if self._path_contains_scope(path): scope_name, path = self._parse_path(path) node = self._get_scope(scope_name) # Otherwise, look for the node in the primary scope. else: node = self._get_primary_scope() return node.node_exists(path) def node_names(self, path=''): """ Return the names of the children of the node at the specified path. """ # If the path contains a specific scope then get the names of the # children of the node in that scope. if self._path_contains_scope(path): scope_name, path = self._parse_path(path) nodes = [self._get_scope(scope_name)] # Otherwise, merge the names of the children of the node in all scopes. else: nodes = self.scopes names = set() for node in nodes: names.update(node.node(path).node_names()) return list(names) ########################################################################### # 'Preferences' protocol. ########################################################################### #### Listener methods #### def add_preferences_listener(self, listener, path=''): """ Add a listener for changes to a node's preferences. """ # If the path contains a specific scope then add a preferences listener # to the node in that scope. if self._path_contains_scope(path): scope_name, path = self._parse_path(path) nodes = [self._get_scope(scope_name)] # Otherwise, add a preferences listener to the node in all scopes. else: nodes = self.scopes for node in nodes: node.add_preferences_listener(listener, path) return def remove_preferences_listener(self, listener, path=''): """ Remove a listener for changes to a node's preferences. """ # If the path contains a specific scope then remove a preferences # listener from the node in that scope. if self._path_contains_scope(path): scope_name, path = self._parse_path(path) nodes = [self._get_scope(scope_name)] # Otherwise, remove a preferences listener from the node in all scopes. else: nodes = self.scopes for node in nodes: node.remove_preferences_listener(listener, path) return #### Persistence methods #### def load(self, file_or_filename=None): """ Load preferences from a file. This loads the preferences into the primary scope. fixme: I'm not sure it is worth providing an implentation here. I think it would be better to encourage people to explicitly reference a particular scope. """ if file_or_filename is None and len(self.filename) > 0: file_or_filename = self.filename node = self._get_primary_scope() node.load(file_or_filename) return def save(self, file_or_filename=None): """ Save the node's preferences to a file. This asks each scope in turn to save its preferences. If a file or filename is specified then it is only passed to the primary scope. """ if file_or_filename is None and len(self.filename) > 0: file_or_filename = self.filename self._get_primary_scope().save(file_or_filename) for scope in self.scopes: if scope is not self._get_primary_scope(): scope.save() return ########################################################################### # 'ScopedPreferences' protocol. ########################################################################### def _application_preferences_filename_default(self): """ Trait initializer. """ return join(ETSConfig.application_home, 'preferences.ini') # fixme: In hindsight, I don't think this class should have provided # default scopes. This should have been an 'abstract' class and then # things like the newer (and preferred) 'ApplicationPreferences' class # layered on top. def _scopes_default(self): """ Trait initializer. """ scopes = [ Preferences( name = 'application', filename = self.application_preferences_filename ), Preferences(name='default') ] return scopes def get_scope(self, scope_name): """ Return the scope with the specified name. Return None if no such scope exists. """ for scope in self.scopes: if scope_name == scope.name: break else: scope = None return scope ########################################################################### # Private protocol. ########################################################################### def _get(self, path, default, nodes, inherit): """ Get a preference from a list of nodes. """ for node in nodes: value = node.get(path, Undefined, inherit) if value is not Undefined: break else: value = default return value def _get_scope(self, scope_name): """ Return the scope with the specified name. Raise a 'ValueError' is no such scope exists. """ scope = self.get_scope(scope_name) if scope is None: raise ValueError('no such scope %s' % scope_name) return scope def _get_primary_scope(self): """ Return the primary scope. By default, this is the first scope. """ if len(self.primary_scope_name) > 0: scope = self._get_scope(self.primary_scope_name) else: scope = self.scopes[0] return scope def _path_contains_scope(self, path): """ Return True if the path contains a scope component. """ return '/' in path def _parse_path(self, path): """ 'Parse' the path into two parts, the scope name and the rest! """ components = path.split('/') return components[0], '/'.join(components[1:]) ########################################################################### # Debugging interface. ########################################################################### def dump(self, indent=''): """ Dump the preferences hierarchy to stdout. """ if indent == '': print print indent, 'Node(%s)' % self.name, self._preferences indent += ' ' for child in self.scopes: child.dump(indent) return #### EOF ###################################################################### apptools-4.1.0/apptools/preferences/preferences.py0000644000175100001440000004211711674464005023367 0ustar ischnellusers00000000000000""" The default implementation of a node in a preferences hierarchy. """ # Standard library imports. import logging, threading # Enthought library imports. from traits.api import Any, Callable, Dict, HasTraits, Instance, List from traits.api import Property, Str, Undefined, implements # Local imports. from i_preferences import IPreferences # Logging. logger = logging.getLogger(__name__) class Preferences(HasTraits): """ The default implementation of a node in a preferences hierarchy. """ implements(IPreferences) #### 'IPreferences' interface ############################################# # The absolute path to this node from the root node (the empty string if # this node *is* the root node). path = Property(Str) # The parent node (None if this node *is* the root node). parent = Instance(IPreferences) # The name of the node relative to its parent (the empty string if this # node *is* the root node). name = Str #### 'Preferences' interface ############################################## # The default name of the file used to persist the preferences (if no # filename is passed in to the 'load' and 'save' methods, then this is # used instead). filename = Str #### Protected 'Preferences' interface #################################### # A lock to make access to the node thread-safe. # # fixme: There *should* be no need to declare this as a trait, but if we # don't then we have problems using nodes in the preferences manager UI. # It is something to do with 'cloning' the node for use in a 'modal' traits # UI... Hmmm... _lk = Any # The node's children. _children = Dict(Str, IPreferences) # The node's preferences. _preferences = Dict(Str, Any) # Listeners for changes to the node's preferences. # # The callable must take 4 arguments, e.g:: # # listener(node, key, old, new) _preferences_listeners = List(Callable) ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **traits): """ Constructor. """ # A lock to make access to the '_children', '_preferences' and # '_preferences_listeners' traits thread-safe. self._lk = threading.Lock() # Base class constructor. super(Preferences, self).__init__(**traits) # If a filename has been specified then load the preferences from it. if len(self.filename) > 0: self.load() return ########################################################################### # 'IPreferences' interface. ########################################################################### #### Trait properties ##################################################### def _get_path(self): """ Property getter. """ names = [] node = self while node.parent is not None: names.append(node.name) node = node.parent names.reverse() return '.'.join(names) #### Methods ############################################################## #### Methods where 'path' refers to a preference #### def get(self, path, default=None, inherit=False): """ Get the value of the preference at the specified path. """ if len(path) == 0: raise ValueError('empty path') components = path.split('.') # If there is only one component in the path then the operation takes # place in this node. if len(components) == 1: value = self._get(path, Undefined) # Otherwise, find the next node and pass the rest of the path to that. else: node = self._get_child(components[0]) if node is not None: value = node.get('.'.join(components[1:]), Undefined) else: value = Undefined # If inherited values are allowed then try those as well. # # e.g. 'acme.ui.widget.bgcolor' # 'acme.ui.bgcolor' # 'acme.bgcolor' # 'bgcolor' while inherit and value is Undefined and len(components) > 1: # Remove the penultimate component... # # e.g. 'acme.ui.widget.bgcolor' -> 'acme.ui.bgcolor' del components[-2] # ... and try that. value = self.get('.'.join(components), default=Undefined) if value is Undefined: value = default return value def remove(self, path): """ Remove the preference at the specified path. """ if len(path) == 0: raise ValueError('empty path') components = path.split('.') # If there is only one component in the path then the operation takes # place in this node. if len(components) == 1: self._remove(path) # Otherwise, find the next node and pass the rest of the path to that. else: node = self._get_child(components[0]) if node is not None: node.remove('.'.join(components[1:])) return def set(self, path, value): """ Set the value of the preference at the specified path. """ if len(path) == 0: raise ValueError('empty path') components = path.split('.') # If there is only one component in the path then the operation takes # place in this node. if len(components) == 1: self._set(path, value) # Otherwise, find the next node (creating it if it doesn't exist) # and pass the rest of the path to that. else: node = self._node(components[0]) node.set('.'.join(components[1:]), value) return #### Methods where 'path' refers to a node #### def clear(self, path=''): """ Remove all preferences from the node at the specified path. """ # If the path is empty then the operation takes place in this node. if len(path) == 0: self._clear() # Otherwise, find the next node and pass the rest of the path to that. else: components = path.split('.') node = self._get_child(components[0]) if node is not None: node.clear('.'.join(components[1:])) return def keys(self, path=''): """ Return the preference keys of the node at the specified path. """ # If the path is empty then the operation takes place in this node. if len(path) == 0: keys = self._keys() # Otherwise, find the next node and pass the rest of the path to that. else: components = path.split('.') node = self._get_child(components[0]) if node is not None: keys = node.keys('.'.join(components[1:])) else: keys = [] return keys def node(self, path=''): """ Return the node at the specified path. """ # If the path is empty then the operation takes place in this node. if len(path) == 0: node = self # Otherwise, find the next node and pass the rest of the path to that. else: components = path.split('.') node = self._node(components[0]) node = node.node('.'.join(components[1:])) return node def node_exists(self, path=''): """ Return True if the node at the specified path exists. """ # If the path is empty then the operation takes place in this node. if len(path) == 0: exists = True # Otherwise, find the next node and pass the rest of the path to that. else: components = path.split('.') node = self._get_child(components[0]) if node is not None: exists = node.node_exists('.'.join(components[1:])) else: exists = False return exists def node_names(self, path=''): """ Return the names of the children of the node at the specified path. """ # If the path is empty then the operation takes place in this node. if len(path) == 0: names = self._node_names() # Otherwise, find the next node and pass the rest of the path to that. else: components = path.split('.') node = self._get_child(components[0]) if node is not None: names = node.node_names('.'.join(components[1:])) else: names = [] return names #### Persistence methods #### def flush(self): """ Force any changes in the node to the backing store. This includes any changes to the node's descendants. """ self.save() return ########################################################################### # 'Preferences' interface. ########################################################################### #### Listener methods #### def add_preferences_listener(self, listener, path=''): """ Add a listener for changes to a node's preferences. """ # If the path is empty then the operation takes place in this node. if len(path) == 0: names = self._add_preferences_listener(listener) # Otherwise, find the next node and pass the rest of the path to that. else: components = path.split('.') node = self._node(components[0]) node.add_preferences_listener(listener, '.'.join(components[1:])) return def remove_preferences_listener(self, listener, path=''): """ Remove a listener for changes to a node's preferences. """ # If the path is empty then the operation takes place in this node. if len(path) == 0: names = self._remove_preferences_listener(listener) # Otherwise, find the next node and pass the rest of the path to that. else: components = path.split('.') node = self._node(components[0]) node.remove_preferences_listener(listener,'.'.join(components[1:])) return #### Persistence methods #### def load(self, file_or_filename=None): """ Load preferences from a file. This is a *merge* operation i.e. the contents of the file are added to the node. This implementation uses 'ConfigObj' files. """ if file_or_filename is None: file_or_filename = self.filename logger.debug('loading preferences from <%s>', file_or_filename) # Do the import here so that we don't make 'ConfigObj' a requirement # if preferences aren't ever persisted (or a derived class chooses to # use a different persistence mechanism). from configobj import ConfigObj config_obj = ConfigObj(file_or_filename) # 'name' is the section name, 'value' is a dictionary containing the # name/value pairs in the section (the actual preferences ;^). for name, value in config_obj.items(): # Create/get the node from the section name. components = name.split('.') node = self for component in components: node = node._node(component) # Add the contents of the section to the node. self._add_dictionary_to_node(node, value) return def save(self, file_or_filename=None): """ Save the node's preferences to a file. This implementation uses 'ConfigObj' files. """ if file_or_filename is None: file_or_filename = self.filename # If no file or filename is specified then don't save the preferences! if len(file_or_filename) > 0: # Do the import here so that we don't make 'ConfigObj' a # requirement if preferences aren't ever persisted (or a derived # class chooses to use a different persistence mechanism). from configobj import ConfigObj logger.debug('saving preferences to <%s>', file_or_filename) config_obj = ConfigObj(file_or_filename) self._add_node_to_dictionary(self, config_obj) config_obj.write() return ########################################################################### # Protected 'Preferences' interface. # # These are the only methods that should access the protected '_children' # and '_preferences' traits. This helps make it easy to subclass this class # to create other implementations (all the subclass has to do is to # implement these protected methods). # ########################################################################### def _add_dictionary_to_node(self, node, dictionary): """ Add the contents of a dictionary to a node's preferences. """ self._lk.acquire() node._preferences.update(dictionary) self._lk.release() return def _add_node_to_dictionary(self, node, dictionary): """ Add a node's preferences to a dictionary. """ # This method never manipulates the '_preferences' trait directly. # Instead it does eveything via the other protected methods and hence # doesn't need to grab the lock. if len(node._keys()) > 0: dictionary[node.path] = {} for key in node._keys(): dictionary[node.path][key] = node._get(key) for name in node._node_names(): self._add_node_to_dictionary(node._get_child(name), dictionary) return def _add_preferences_listener(self, listener): """ Add a listener for changes to thisnode's preferences. """ self._lk.acquire() self._preferences_listeners.append(listener) self._lk.release() return def _clear(self): """ Remove all preferences from this node. """ self._lk.acquire() self._preferences.clear() self._lk.release() return def _create_child(self, name): """ Create a child of this node with the specified name. """ self._lk.acquire() child = self._children[name] = Preferences(name=name, parent=self) self._lk.release() return child def _get(self, key, default=None): """ Get the value of a preference in this node. """ self._lk.acquire() value = self._preferences.get(key, default) self._lk.release() return value def _get_child(self, name): """ Return the child of this node with the specified name. Return None if no such child exists. """ self._lk.acquire() child = self._children.get(name) self._lk.release() return child def _keys(self): """ Return the preference keys of this node. """ self._lk.acquire() keys = self._preferences.keys() self._lk.release() return keys def _node(self, name): """ Return the child of this node with the specified name. Create the child node if it does not exist. """ node = self._get_child(name) if node is None: node = self._create_child(name) return node def _node_names(self): """ Return the names of the children of this node. """ self._lk.acquire() node_names = self._children.keys() self._lk.release() return node_names def _remove(self, name): """ Remove a preference value from this node. """ self._lk.acquire() if name in self._preferences: del self._preferences[name] self._lk.release() return def _remove_preferences_listener(self, listener): """ Remove a listener for changes to the node's preferences. """ self._lk.acquire() if listener in self._preferences_listeners: self._preferences_listeners.remove(listener) self._lk.release() return def _set(self, key, value): """ Set the value of a preference in this node. """ # Preferences are *always* stored as strings. value = str(value) self._lk.acquire() old = self._preferences.get(key) self._preferences[key] = value # If the value is unchanged then don't call the listeners! if old == value: listeners = [] else: listeners = self._preferences_listeners[:] self._lk.release() for listener in listeners: listener(self, key, old, value) return ########################################################################### # Debugging interface. ########################################################################### def dump(self, indent=''): """ Dump the preferences hierarchy to stdout. """ if indent == '': print print indent, 'Node(%s)' % self.name, self._preferences indent += ' ' for child in self._children.values(): child.dump(indent) return #### EOF ###################################################################### apptools-4.1.0/apptools/preferences/__init__.py0000644000175100001440000000025211674464005022617 0ustar ischnellusers00000000000000# Copyright (c) 2007-2011 by Enthought, Inc. # All rights reserved. """ Manages application preferences. Part of the AppTools project of the Enthought Tool Suite """ apptools-4.1.0/apptools/preferences/package_globals.py0000644000175100001440000000137411674464005024164 0ustar ischnellusers00000000000000""" Package-scope globals. The default preferences node is currently used by 'PreferencesHelper' and 'PreferencesBinding' instances if no specific preferences node is set. This makes it easy for them to access the root node of an application-wide preferences hierarchy. """ # The default preferences node. _default_preferences = None def get_default_preferences(): """ Get the default preferences node. """ return _default_preferences def set_default_preferences(default_preferences): """ Set the default preferences node. """ global _default_preferences _default_preferences = default_preferences # For convenience. return _default_preferences #### EOF ###################################################################### apptools-4.1.0/apptools/help/0000755000175100001440000000000011674464005017136 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/help/help_plugin/0000755000175100001440000000000011674464005021444 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/help/help_plugin/api.py0000644000175100001440000000114211674464005022565 0ustar ischnellusers00000000000000""" Help Plugin API :Copyright: 2008, Enthought Inc. :License: BSD :Author: Janet Swisher """ # This software is provided without warranty under the terms of the BSD # license included in AppTools/trunk/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! from help_code import HelpCode from help_doc import HelpDoc from help_plugin import HelpPlugin from i_help_code import IHelpCode from i_help_doc import IHelpDoc apptools-4.1.0/apptools/help/help_plugin/preferences.ini0000644000175100001440000000053611674464005024452 0ustar ischnellusers00000000000000 # Entries for testing purposes #[enthought.help.help_plugin.TraitsDemo] #label = Traits Demo #filename = C:\Documents and Settings\demo\src\Traits_3.0.3\examples\demo\demo.py #[enthought.help.help_plugin.AcmeLab] #label = Envisage AcmeLab #filename = C:\Documents and Settings\demo\src\EnvisagePlugins_3.0.1\examples\workbench\AcmeLab apptools-4.1.0/apptools/help/help_plugin/help_doc.py0000644000175100001440000000275411674464005023603 0ustar ischnellusers00000000000000""" The help doc implementation. :Copyright: 2008, Enthought Inc. :License: BSD :Author: Janet Swisher """ # This software is provided without warranty under the terms of the BSD # license included in AppTools/trunk/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! from apptools.preferences.api import PreferencesHelper from traits.api import Either, File, Str, implements, Bool from i_help_doc import IHelpDoc class HelpDoc(PreferencesHelper): """ The implementation for help docs. A help doc is defined by a UI label, a filename, and a viewer program. """ implements(IHelpDoc) #### IHelpDoc interface / Preferences ###################################### # NOTE: This class inherits preferences_path from PreferencesHelper. # The UI label for the help doc, which appears in menus or dialogs. label = Str # The path to the document, which can be full, or relative to the Python # installation directory (sys.prefix). filename = File # Is this a url? url = Bool(False) # The program to use to view the document. 'browser' means the platform # default web browser. Otherwise, it is a command to run, which may be # in the program search path of the current environment, or an absolute # path to a program. viewer = Either('browser', Str) apptools-4.1.0/apptools/help/help_plugin/help_plugin.py0000644000175100001440000002670311674464005024334 0ustar ischnellusers00000000000000""" The definition of an Envisage plugin for online help. It assumes that the Workbench plugin is being used. :Copyright: 2008, Enthought Inc. :License: BSD :Author: Janet Swisher """ # This software is provided without warranty under the terms of the BSD # license included in AppTools/trunk/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # Standard library imports. import logging import new # Enthought libary imports from envisage.api import Plugin, ExtensionPoint from envisage.ui.action.api import ActionSet, Group, Menu from traits.api import Instance, List, Str # Local imports from help_code import HelpCode from help_doc import HelpDoc from i_help_code import IHelpCode from i_help_doc import IHelpDoc # Logging. logger = logging.getLogger(__name__) HELP_MENU = 'MenuBar/Help' DOCS_MENU = 'Documents' DOCS_GROUP = 'DocsGroup' DEMOS_MENU = 'Demos' DEMOS_GROUP = 'DemosGroup' EXAMPLES_MENU = 'Examples' EXAMPLES_GROUP = 'ExamplesGroup' DOWNLOADS_MENU = 'Downloads' # This module's package. PKG = '.'.join(__name__.split('.')[:-1]) ################################################################################ # The plugin ################################################################################ class HelpPlugin(Plugin): """ A Help plugin for Envisage Workbench applications. This plugin displays items on the Workbench Help menu, based on contributions from itself or other plugins. """ # IDs of extension points this plugin offers. HELP_DOCS = PKG + '.help_docs' HELP_DEMOS = PKG + '.help_demos' HELP_EXAMPLES = PKG + '.help_examples' HELP_DOWNLOADS = PKG + '.help_downloads' # IDs of extension points this plugin contributes to. WORKBENCH_ACTION_SETS='envisage.ui.workbench.action_sets' PREFERENCES = 'envisage.preferences' PREFERENCES_PAGES = 'envisage.ui.workbench.preferences_pages' #### 'IPlugin' interface ################################################## # The plugin's unique identifier. id = 'apptools.help.help_plugin' # The plugin's name (suitable for displaying to the user). name = 'Help Plugin' #### public 'HelpPlugin' interface ######################################## # List of menu names added to the main toolbar by this plugin # (These are placed before the 'Help' Menu). menus = List(Str) #### Extension points offered by this plugin ############################## help_docs = ExtensionPoint( List(IHelpDoc), id=HELP_DOCS, desc=""" A help doc is defined by a preference node that specifies a UI label, a filename for the document, and a (path to a) viewer for viewing the document. Each contribution to this extension point must be an instance of a class that implements IHelpDoc. The easiest way to do this is to create an instance of `apptools.help.help_plugin.api.HelpDoc`. So, to contribute a help doc: 1. Create a preferences file for your plugin if it doesn't already have one. (Be sure to contribute your preferences file to the `envisage.preferences` extension point.) 2. Define a unique "node" (section heading) in your preferences file for each document, and specify values for the 'label', 'viewer', and 'filename' settings. (Use 'browser' as the value of 'viewer' if the document can be viewed in a web browser.) 3. For each document, contribute a HelpDoc to this extension point, and specify its *preferences_path* as the corresponding node name from your preferences file. """ ) help_demos = ExtensionPoint( List(IHelpCode), id=HELP_DEMOS, desc=""" A help demo is a Python program that is run when it is selected from the Help>Demos submenu. It is defined by a preference node that specifies a UI label and a filename for the demo entry point. Each contribution to this extension point must be an instance of a class that implements IHelpCode. The easiest way to do this is to create an instance of `apptools.help.help_plugin.api.HelpCode`. So, to contribute a help demo: 1. Create a preferences file for your plugin if it doesn't already have one. (Be sure to contribute your preferences file to the `envisage.preferences` extension point.) 2. Define a unique "node" (section heading) in your preferences file for each demo, and specify values for the 'label' and 'filename' settings. (Note that the same preferences section can be used for a help demo and a help example.) 3. For each demo, contribute a HelpDemo to this extension point, and specify its *preferences_path* as the corresponding node name from your preferences file. """ ) help_examples = ExtensionPoint( List(IHelpCode), id=HELP_EXAMPLES, desc=""" A help example is a Python file that is opened for viewing when it is selected from the Help>Examples submenu. It is defined by a preference node that specifies a UI label and a filename for the primary example file. Each contribution to this extension point must be an instance of a class that implements IHelpCode. The easiest way to do this is to create an instance of `apptools.help.help_plugin.api.HelpCode`. So, to contribute a help example: 1. Create a preferences file for your plugin if it doesn't already have one. (Be sure to contribute your preferences file to the `envisage.preferences` extension point.) 2. Define a unique "node" (section heading) in your preferences file for each example, and specify values for the 'label' and 'filename' settings. (Note that the same preferences section can be used for a help demo and a help example.) 3. For each example, contribute a HelpCode to this extension point, and specify its *preferences_path* as the corresponding node name from your preferences file. """ ) help_downloads = ExtensionPoint( List(IHelpDoc), id=HELP_DOWNLOADS, desc=""" A help download is a url that is opened in a browser for viewing when it is selected from the Help>Downloads submenu. It is defined by a preference node that specifies a UI label and a url for the download. Each contribution to this extension point must be an instance of a class that implements IHelpDoc, and has the url trait set to True. The easiest way to do this is to create an instance of `apptools.help.help_plugin.api.HelpDoc`. So, to contribute a help doc: 1. Create a preferences file for your plugin if it doesn't already have one. (Be sure to contribute your preferences file to the `envisage.preferences` extension point.) 2. Define a unique "node" (section heading) in your preferences file for each url, and specify values for the 'label' and 'filename' settings. Also set 'url' to True. 3. For each document, contribute a HelpDoc to this extension point, and specify its *preferences_path* as the corresponding node name from your preferences file. """ ) # FIXME: Ideally, there should be an extension point to allow plugins to # offer editors to display examples (e.g., TextEditorPlugin or # RemoteEditorPlugin). For now, examples open in an external editor # launched with subprocess.Popen. The user can set the editor # command in the Examples preferences page. ###### Contributions to extension points made by this plugin ###### help_action_sets = List(contributes_to=WORKBENCH_ACTION_SETS) def _help_action_sets_default(self): """ Returns a list containing an action set class whose **actions** correspond to the help docs in the help_docs extension point. """ extension_point_mapping = { DOCS_MENU: self.help_docs, EXAMPLES_MENU: self.help_examples, DEMOS_MENU: self.help_demos, DOWNLOADS_MENU: self.help_downloads} # Construct traits for the action set ns = {'id': 'apptools.help.help_plugin.help_action_set', 'name': 'Help Plugin ActionSet', 'groups': [ Group( id=DOCS_GROUP, before='AboutGroup', path=HELP_MENU ) ] } for (menu_name, items) in extension_point_mapping.items(): if len(items) > 0: menu = Menu( name = menu_name, class_name = PKG + '.help_submenu_manager:%sMenuManager' % menu_name ) if menu_name in self.menus: menu.path = 'MenuBar' menu.before = 'Help' else: menu.path = HELP_MENU menu.group = DOCS_GROUP # Append the menu. ns.setdefault('menus', []).append(menu) return [new.classobj('SPLHelpActionSet', (ActionSet,), ns)] preferences = List(contributes_to=PREFERENCES) def _preferences_default(self): return ['pkgfile://%s/preferences.ini' % PKG] preferences_pages = List(contributes_to=PREFERENCES_PAGES) def _preferences_pages_default(self): from apptools.help.help_plugin.preferences_pages import \ DocumentsPreferencesPage, DemosPreferencesPage, \ ExamplesPreferencesPage, HelpDocPreferencesPage, \ HelpDemoPreferencesPage, HelpExamplePreferencesPage pages = [] if len(self.help_docs) > 0: pages.append(DocumentsPreferencesPage) pages.extend( [ new.classobj(doc.preferences_path + 'PreferencesPage', (HelpDocPreferencesPage,), {'preferences_path': doc.preferences_path}, ) for doc in self.help_docs ]) if len(self.help_demos) > 0: pages.append(DemosPreferencesPage) pages.extend( [ new.classobj(demo.preferences_path + 'PreferencesPage', (HelpDemoPreferencesPage,), {'preferences_path': demo.preferences_path}, ) for demo in self.help_demos ]) if len(self.help_examples) > 0: pages.append(ExamplesPreferencesPage) pages.extend( [ new.classobj(example.preferences_path + 'PreferencesPage', (HelpExamplePreferencesPage,), {'preferences_path': example.preferences_path}, ) for example in self.help_examples ]) return pages #my_help_demos = List(contributes_to=HELP_DEMOS) #def _my_help_demos_default(self): # return [HelpCode( preferences_path=PKG + '.TraitsDemo'), # ] #my_help_examples = List(contributes_to=HELP_EXAMPLES) #def _my_help_examples_default(self): # return [HelpCode( preferences_path=PKG + '.AcmeLab'), # ] apptools-4.1.0/apptools/help/help_plugin/i_help_code.py0000644000175100001440000000224611674464005024254 0ustar ischnellusers00000000000000""" The help code interface. This may be used to define examples to be displayed or demos to be run. :Copyright: 2008, Enthought Inc. :License: BSD :Author: Janet Swisher """ # This software is provided without warranty under the terms of the BSD # license included in AppTools/trunk/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! from traits.api import Interface, File, Str class IHelpCode(Interface): """ The interface for help code. A help code is defined by a UI label and a filename. """ # The UI label for the demo, which appears in menus or dialogs. label = Str # The path to the file containing the code entry point. This may be # absolute, or relative to the Python directory (sys.prefix). filename = File # The unique ID of the preferences node that contains the other values for # this object preferences_path = Str # The code to execute. This is executed when filename is None or an empty # string. code = Str apptools-4.1.0/apptools/help/help_plugin/action/0000755000175100001440000000000011674464005022721 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/help/help_plugin/action/util.py0000644000175100001440000000131611674464005024251 0ustar ischnellusers00000000000000""" Utility functions for help plugin actions. :Copyright: 2008, Enthought Inc. :License: BSD :Author: Janet Swisher """ # This software is provided without warranty under the terms of the BSD # license included in AppTools/trunk/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! from os.path import isabs, join, normpath import sys def get_sys_prefix_relative_filename(filename): return None if (filename is None) else \ filename if isabs(filename) else \ normpath(join(sys.prefix, filename)) apptools-4.1.0/apptools/help/help_plugin/action/images/0000755000175100001440000000000011674464005024166 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/help/help_plugin/action/images/document.png0000644000175100001440000000051511674464005026513 0ustar ischnellusers00000000000000‰PNG  IHDRóÿabKGDÿÿÿ ½§“ pHYs  šœtIMEÕ8º`&BÚIDAT8Ë­’=n„0…¿H»[!!ŸŸ³l™"ÇAæ!QÚsú­LŠˆ$^X¢¼j,{Þ|3µmû1 à ÇôZ×õ;ÖÚ騬µÓì”ÌÁ8Žxït/5Ç’ˆã˜4MW(wï=ιUÂR’¨ªêW/ÉòPÅ&AHÉòá’à§ž"Èó #------------------------------------------------------------------------------ """ Copied from Python Cookbook. """ class RingBuffer: def __init__(self,size_max): self.max = size_max self.data = [] def append(self,x): """append an element at the end of the buffer""" self.data.append(x) if len(self.data) == self.max: self.cur = 0 self.__class__ = RingBufferFull def get(self): """ return a list of elements from the oldest to the newest""" return self.data class RingBufferFull: def __init__(self,n): raise Exception("you should use RingBuffer") def append(self,x): self.data[self.cur]=x self.cur=(self.cur+1) % self.max def get(self): return self.data[self.cur:]+self.data[:self.cur] # sample of use """x=RingBuffer(5) x.append(1); x.append(2); x.append(3); x.append(4) print x.__class__,x.get() x.append(5) print x.__class__,x.get() x.append(6) print x.data,x.get() x.append(7); x.append(8); x.append(9); x.append(10) print x.data,x.get()""" apptools-4.1.0/apptools/logger/util.py0000644000175100001440000000730411674464005021020 0ustar ischnellusers00000000000000""" Utility functions. fixme: I don't like random collections of utility functions! Where should this go? """ # Standard library imports. import os from os.path import basename, dirname, isdir, splitdrive, splitext from zipfile import is_zipfile, ZipFile def get_module_name(filename): """ Get the fully qualified module name for a filename. For example, if the filename is /enthought/envisage/core/core_plugin_definition.py this method would return envisage.core.core_plugin_definition """ if os.path.exists(filename): # Get the name of the module minus the '.py' module, ext = os.path.splitext(os.path.basename(filename)) # Start with the actual module name. module_path = [module] # If the directory is a Python package then add it to the module path. #return self.is_folder and '__init__.py' in os.listdir(self.path) parent = dirname(filename) while isdir(parent) and '__init__.py' in os.listdir(parent): bname = basename(parent) module_path.insert(0, splitext(bname)[0]) parent = dirname(parent) module_name = '.'.join(module_path) # If the file does not exist then it might be a zip file path. else: module_name = get_module_name_from_zip(filename) return module_name # fixme: WIP def get_module_name_from_zip(filename): # first, find the zip file in the path filepath = filename zippath = None while not is_zipfile(filepath) and \ splitdrive(filepath)[1] != '\\' \ and splitdrive(filepath)[1] != '/': filepath, tail = os.path.split(filepath) if zippath is not None: zippath = tail + '/' + zippath else: zippath = tail if not is_zipfile(filepath): return None # if the split left a preceding slash on the zippath then remove # it if zippath.startswith('\\') or zippath.startswith('/'): zippath = zippath[1:] # replace any backwards slashes with forward slashes zippath = zippath.replace('\\', '/') # Get the name of the module minus the '.py' module, ext = splitext(basename(zippath)) # Start with the actual module name. module_path = [module] # to get the module name, we walk through the zippath until we # find a parent directory that does NOT have a __init__.py file z = ZipFile(filepath) parentpath = dirname(zippath) while path_exists_in_zip(z, parentpath + '/__init__.py'): module_path.insert(0, basename(parentpath)) parentpath = dirname(parentpath) z.close() return '.'.join(module_path) # fixme: WIP def path_exists_in_zip(zfile, path): try: zfile.getinfo(path) exists = True except: exists = False return exists # fixme: WIP def is_zip_path(path): """ Returns True if the path refers to a zip file. """ filepath = path while not is_zipfile(filepath) and \ splitdrive(filepath)[1] != '\\' \ and splitdrive(filepath)[1] != '/': filepath = dirname(filepath) return is_zipfile(filepath) # fixme: WIP def get_zip_path(filename): """ Returns the path to the zip file contained in the filename. fixme: An example here would help. """ filepath = filename zippath = None while not is_zipfile(filepath) and \ splitdrive(filepath)[1] != '\\' \ and splitdrive(filepath)[1] != '/': filepath, tail = os.path.split(filepath) if zippath is not None: zippath = tail + '/' + zippath else: zippath = tail return zippath #### EOF ###################################################################### apptools-4.1.0/apptools/logger/plugin/0000755000175100001440000000000011674464005020763 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/logger/plugin/logger_preferences.py0000644000175100001440000000145211674464005025177 0ustar ischnellusers00000000000000import logging from apptools.preferences.api import PreferencesHelper from traits.api import Bool, Str, Trait class LoggerPreferences(PreferencesHelper): """ The persistent service exposing the Logger plugin's API. """ #### Preferences ########################################################### # The log levels level = Trait('Info', {'Debug' : logging.DEBUG, 'Info' : logging.INFO, 'Warning' : logging.WARNING, 'Error' : logging.ERROR, 'Critical' : logging.CRITICAL, }, is_str = True, ) enable_agent = Bool(False) smtp_server = Str() to_address = Str() from_address = Str() # The path to the preferences node that contains the preferences. preferences_path = Str('apptools.logger') apptools-4.1.0/apptools/logger/plugin/preferences.ini0000644000175100001440000000015211674464005023763 0ustar ischnellusers00000000000000[enthought.logger] level = 'Info' enable_agent = False smtp_server = '' to_address = '' from_address = '' apptools-4.1.0/apptools/logger/plugin/logger_service.py0000644000175100001440000001163711674464005024344 0ustar ischnellusers00000000000000# Standard library imports from cStringIO import StringIO import logging import os import zipfile # Enthought library imports from pyface.workbench.api import View as WorkbenchView from traits.api import Any, Callable, HasTraits, Instance, List, \ Property, Undefined, on_trait_change root_logger = logging.getLogger() logger = logging.getLogger(__name__) class LoggerService(HasTraits): """ The persistent service exposing the Logger plugin's API. """ # The Envisage application. application = Any() # The logging Handler we use. handler = Any() # Our associated LoggerPreferences. preferences = Any() # The view we use. plugin_view = Instance(WorkbenchView) # Contributions from other plugins. mail_files = Property(List(Callable)) def save_preferences(self): """ Save the preferences. """ self.preferences.preferences.save() def whole_log_text(self): """ Return all of the logged data as formatted text. """ lines = [ self.handler.format(rec) for rec in self.handler.get() ] # Ensure that we end with a newline. lines.append('') text = '\n'.join(lines) return text def create_email_message(self, fromaddr, toaddrs, ccaddrs, subject, priority, include_userdata=False, stack_trace="", comments="", include_environment=True): """ Format a bug report email from the log files. """ from email.mime.application import MIMEApplication from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText message = MIMEMultipart() message['Subject'] = "%s [priority=%s]" % (subject, priority) message['To'] = ', '.join(toaddrs) message['Cc'] = ', '.join(ccaddrs) message['From'] = fromaddr message.preamble = 'You will not see this in a MIME-aware mail ' \ 'reader.\n' message.epilogue = ' ' # To guarantee the message ends with a newline # First section is simple ASCII data ... m = [] m.append("Bug Report") m.append("==============================") m.append("") if len(comments) > 0: m.append("Comments:") m.append("========") m.append(comments) m.append("") if len(stack_trace) > 0: m.append("Stack Trace:") m.append("===========") m.append(stack_trace) m.append("") msg = MIMEText('\n'.join(m)) message.attach(msg) # Include the log file ... logtext = self.whole_log_text() msg = MIMEText(logtext) msg.add_header('Content-Disposition', 'attachment', filename='logfile.txt') message.attach(msg) # Include the environment variables ... # FIXME: ask the user, maybe? if include_environment: # Transmit the user's environment settings as well. Main purpose is # to work out the user name to help with following up on bug reports # and in future we should probably send less data. entries = [] for key, value in sorted(os.environ.items()): entries.append('%30s : %s\n' % (key, value)) msg = MIMEText(''.join(entries)) msg.add_header('Content-Disposition', 'attachment', filename='environment.txt') message.attach(msg) if include_userdata and len(self.mail_files) != 0: f = StringIO() zf = zipfile.ZipFile(f, 'w') for mf in self.mail_files: mf(zf) zf.close() msg = MIMEApplication(f.getvalue()) msg.add_header('Content-Disposition', 'attachment', filename='userdata.zip') message.attach(msg) return message def send_bug_report(self, smtp_server, fromaddr, toaddrs, ccaddrs, message): """ Send a bug report email. """ try: import smtplib logger.debug("Connecting to: %s" % smtp_server) server = smtplib.SMTP(host=smtp_server) logger.debug("Connected: %s" % server) #server.set_debuglevel(1) server.sendmail(fromaddr, toaddrs + ccaddrs, message.as_string()) server.quit() except Exception, e: logger.exception("Problem sending error report") #### Traits stuff ######################################################### def _get_mail_files(self): return self.application.get_extensions( 'apptools.logger.plugin.mail_files') @on_trait_change('preferences.level_') def _level_changed(self, new): if (new is not None and new is not Undefined and self.handler is not None): root_logger.setLevel(self.preferences.level_) self.handler.setLevel(self.preferences.level_) apptools-4.1.0/apptools/logger/plugin/logger_plugin.py0000644000175100001440000000731611674464005024201 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Logger plugin. """ # Standard library imports. import logging # Enthought library imports. from envisage.api import ExtensionPoint, Plugin from apptools.logger.log_queue_handler import LogQueueHandler from traits.api import Callable, List # Local imports. from logger_preferences import LoggerPreferences from logger_service import LoggerService ID = 'apptools.logger' ILOGGER = ID + '.plugin.logger_service.LoggerService' class LoggerPlugin(Plugin): """ Logger plugin. """ id = ID name = 'Logger plugin' #### Extension points for this plugin ###################################### MAIL_FILES = 'apptools.logger.plugin.mail_files' mail_files = ExtensionPoint( List(Callable), id=MAIL_FILES, desc=""" This extension point allows you to contribute functions which will be called to add project files to the zip file that the user mails back with bug reports from the Quality Agent. The function will be passed a zipfile.ZipFile object. """ ) #### Contributions to extension points made by this plugin ################# PREFERENCES = 'envisage.preferences' PREFERENCES_PAGES = 'envisage.ui.workbench.preferences_pages' VIEWS = 'envisage.ui.workbench.views' preferences = List(contributes_to=PREFERENCES) preferences_pages = List(contributes_to=PREFERENCES_PAGES) views = List(contributes_to=VIEWS) def _preferences_default(self): return ['pkgfile://%s/plugin/preferences.ini' % ID] def _preferences_pages_default(self): from apptools.logger.plugin.view.logger_preferences_page import \ LoggerPreferencesPage return [LoggerPreferencesPage] def _views_default(self): return [self._logger_view_factory] #### Plugin interface ###################################################### def start(self): """ Starts the plugin. """ preferences = LoggerPreferences() service = LoggerService(application=self.application, preferences=preferences) formatter = logging.Formatter('%(levelname)s|%(asctime)s|%(message)s') handler = LogQueueHandler() handler.setLevel(preferences.level_) handler.setFormatter(formatter) root_logger = logging.getLogger() root_logger.addHandler(handler) root_logger.setLevel(preferences.level_) service.handler = handler self.application.register_service(ILOGGER, service) def stop(self): """ Stops the plugin. """ service = self.application.get_service(ILOGGER) service.save_preferences() #### LoggerPlugin private interface ######################################## def _logger_view_factory(self, **traits): from apptools.logger.plugin.view.logger_view import LoggerView service = self.application.get_service(ILOGGER) view = LoggerView(service=service, **traits) # Record the created view on the service. service.plugin_view = view return view #### EOF ###################################################################### apptools-4.1.0/apptools/logger/plugin/__init__.py0000644000175100001440000000000011674464005023062 0ustar ischnellusers00000000000000apptools-4.1.0/apptools/logger/plugin/view/0000755000175100001440000000000011674464005021735 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/logger/plugin/view/images/0000755000175100001440000000000011674464005023202 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/logger/plugin/view/images/error.png0000644000175100001440000000701511674464005025044 0ustar ischnellusers00000000000000‰PNG  IHDRóÿa pHYs  šœ OiCCPPhotoshop ICC profilexÚSgTSé=÷ÞôBKˆ€”KoR RB‹€‘&*! Jˆ!¡ÙQÁEEÈ ˆŽŽ€ŒQ, Š Øä!¢Žƒ£ˆŠÊûá{£kÖ¼÷æÍþµ×>ç¬ó³ÏÀ –H3Q5€ ©BàƒÇÄÆáä.@ $p³d!sý#ø~<<+"À¾xÓ ÀM›À0‡ÿêB™\€„Àt‘8K€@zŽB¦@F€˜&S `ËcbãP-`'æÓ€ø™{[”! ‘ eˆDh;¬ÏVŠEX0fKÄ9Ø-0IWfH°·ÀÎ ² 0Qˆ…){`È##x„™FòW<ñ+®ç*x™²<¹$9E[-qWW.(ÎI+6aaš@.Ây™24àóÌ ‘àƒóýxήÎÎ6޶_-ê¿ÿ"bbãþåÏ«p@át~Ñþ,/³€;€mþ¢%îh^  u÷‹f²@µ éÚWópø~<ß5°j>{‘-¨]cöK'XtÀâ÷ò»oÁÔ(€hƒáÏwÿï?ýG %€fI’q^D$.Tʳ?ÇD *°AôÁ,ÀÁÜÁ ü`6„B$ÄÂBB d€r`)¬‚B(†Í°*`/Ô@4ÀQh†“p.ÂU¸=púažÁ(¼ AÈa!ÚˆbŠX#Ž™…ø!ÁH‹$ ɈQ"K‘5H1RŠT UHò=r9‡\Fº‘;È2‚ü†¼G1”²Q=Ô µC¹¨7„F¢ Ðdt1š ›Ðr´=Œ6¡çЫhÚ>CÇ0Àè3Äl0.ÆÃB±8, “c˱"¬ «Æ°V¬»‰õcϱwEÀ 6wB aAHXLXNØH¨ $4Ú 7 „QÂ'"“¨K´&ºùÄb21‡XH,#Ö/{ˆCÄ7$‰C2'¹I±¤TÒÒFÒnR#é,©›4H#“ÉÚdk²9”, +È…ääÃä3ää!ò[ b@q¤øSâ(RÊjJåå4åe˜2AU£šRݨ¡T5ZB­¡¶R¯Q‡¨4uš9̓IK¥­¢•Óhh÷i¯ètºÝ•N—ÐWÒËéGè—èôw †ƒÇˆg(›gw¯˜L¦Ó‹ÇT071ë˜ç™™oUX*¶*|‘Ê •J•&•*/T©ª¦ªÞª UóUËT©^S}®FU3Sã© Ô–«UªPëSSg©;¨‡ªg¨oT?¤~Yý‰YÃLÃOC¤Q ±_ã¼Æ c³x,!k «†u5Ä&±ÍÙ|v*»˜ý»‹=ª©¡9C3J3W³Ró”f?ã˜qøœtN ç(§—ó~ŠÞï)â)¦4L¹1e\kª–—–X«H«Q«Gë½6®í§¦½E»YûAÇJ'\'GgÎçSÙSݧ §M=:õ®.ªk¥¡»Dw¿n§î˜ž¾^€žLo§Þy½çú}/ýTýmú§õG X³ $Û Î<Å5qo</ÇÛñQC]Ã@C¥a•a—á„‘¹Ñ<£ÕFFŒiÆ\ã$ãmÆmÆ£&&!&KMêMîšRM¹¦)¦;L;LÇÍÌÍ¢ÍÖ™5›=1×2ç›ç›×›ß·`ZxZ,¶¨¶¸eI²äZ¦Yî¶¼n…Z9Y¥XUZ]³F­­%Ö»­»§§¹N“N«žÖgðñ¶É¶©·°åØÛ®¶m¶}agbg·Å®Ã“}º}ý= ‡Ù«Z~s´r:V:ޚΜî?}Åô–é/gXÏÏØ3ã¶Ë)ÄiS›ÓGgg¹sƒóˆ‹‰K‚Ë.—>.›ÆÝȽäJtõq]ázÒõ›³›Âí¨Û¯î6îiî‡ÜŸÌ4Ÿ)žY3sÐÃÈCàQåÑ? Ÿ•0k߬~OCOgµç#/c/‘W­×°·¥wª÷aï>ö>rŸã>ã<7Þ2ÞY_Ì7À·È·ËOÃož_…ßC#ÿdÿzÿѧ€%g‰A[ûøz|!¿Ž?:Ûeö²ÙíAŒ ¹AA‚­‚åÁ­!hÈì­!÷ç˜Î‘Îi…P~èÖÐaæa‹Ã~ '…‡…W†?ŽpˆXÑ1—5wÑÜCsßDúD–DÞ›g1O9¯-J5*>ª.j<Ú7º4º?Æ.fYÌÕXXIlK9.*®6nl¾ßüíó‡ââ ã{˜/È]py¡ÎÂô…§©.,:–@LˆN8”ðA*¨Œ%òw%Ž yÂÂg"/Ñ6шØC\*NòH*Mz’쑼5y$Å3¥,幄'©¼L LÝ›:žšv m2=:½1ƒ’‘qBª!M“¶gêgæfvˬe…²þÅn‹·/•Ék³¬Y- ¶B¦èTZ(×*²geWf¿Í‰Ê9–«ž+Íí̳ÊÛ7œïŸÿíÂá’¶¥†KW-X潬j9²‰Š®Û—Ø(Üxå‡oÊ¿™Ü”´©«Ä¹dÏfÒféæÞ-ž[–ª—æ—n ÙÚ´ ßV´íõöEÛ/—Í(Û»ƒ¶C¹£¿<¸¼e§ÉÎÍ;?T¤TôTúT6îÒݵa×ønÑî{¼ö4ìÕÛ[¼÷ý>ɾÛUUMÕfÕeûIû³÷?®‰ªéø–ûm]­NmqíÇÒý#¶×¹ÔÕÒ=TRÖ+ëGǾþïw- 6 UœÆâ#pDyäé÷ ß÷ :ÚvŒ{¬áÓvg/jBšòšF›Sšû[b[ºOÌ>ÑÖêÞzüGÛœ499â?rýéü§CÏdÏ&žþ¢þË®/~øÕë×Îјѡ—ò—“¿m|¥ýêÀë¯ÛÆÂƾÉx31^ôVûíÁwÜwï£ßOä| (ÿhù±õSЧû“““ÿ˜óüc3-ÛgAMA±Ž|ûQ“ cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅF(IDATxÚd“Ío”eÅÏ3ï|´ÓÎà8öCŠÚ¤¨›Ò%éWŒ vD(%"ƒiH¨ñ ]T °qaY`"Á€ŠI ºh')I#&l © iikgÚ™v:ï̼ï3ï\ЦƳ»7çœÜ䞣D„íø<‘H½‹íÒ(P ‘*,g2³ß-.ölç[ۇϚ›'¿½|9 Ö>Ð>@Z(Úv˧ ·¿O§»65jó‚OâñÉÑÑÑdxj ¼*kŸh%` äÛÛeðĉÔÙl×–Á¡htò› ’ ©K…Ká0o‰à¯¯¥qm›‡Z³ÓqhŒÅXìè/OJý´±Ñå{xþ|jøôé‚7o2¿ºJñÈ^döî]"ÓÓ”WW™nm%1<̲1dïÜ!4?¯Þ9z´å‹îî÷éDæ~íì”Ûmmò`hH6±žËɽþ~¹ü¸ …­ý_'OʽŽùcß>9XW7c×õþ\_§¶R!~ëѽ{yu`€ÈŽt^¹J¡µàé¥Kd'&È‹PÙØ jŒg”JÄ«Uò¶=4D)—#qìØ–Ðó<–.^dúìY6ü~Vµ&¯¬jµJzy™’®Rä]k|œðþýhŸ”¢R.s|œôÊ N À?"¬+EU­•â cØm õ®Kco/o|M¡X¤èlÇ¡èöœ;‡ÿ½$%×¥Æ^4”Â× ~Õ©T´ÞóhééaÏÈ®ë‚ßOåÚ5¼Ç)¿þN¹Lc_/êÑ#‚óóÄC!fµÎYÊq|éXÌ{³Tò½ËÎdXe'ucc¬9ƒ¶,¢–ÅB_ñ¿gÙ½¶F¶¾ž©Ú°£W2!%"ðûº›››ÞÍçu¨­H2‰{õ*¾gIËÂ:|{b÷É~‹Dœß3¿T*‰­(,H$š>4F‹1jkñ)…R ñ<*¶  œss™ë®›øO†B ­­M‰h¿Rðü"BE„J¹×gfÒ?;NâeÚÄ¡šš§ÑxÜ<¯ªR Od3™šËå]Ûùÿ:)†œ [šIEND®B`‚apptools-4.1.0/apptools/logger/plugin/view/images/debug.png0000644000175100001440000000715411674464005025005 0ustar ischnellusers00000000000000‰PNG  IHDRóÿa pHYs  šœ OiCCPPhotoshop ICC profilexÚSgTSé=÷ÞôBKˆ€”KoR RB‹€‘&*! Jˆ!¡ÙQÁEEÈ ˆŽŽ€ŒQ, Š Øä!¢Žƒ£ˆŠÊûá{£kÖ¼÷æÍþµ×>ç¬ó³ÏÀ –H3Q5€ ©BàƒÇÄÆáä.@ $p³d!sý#ø~<<+"À¾xÓ ÀM›À0‡ÿêB™\€„Àt‘8K€@zŽB¦@F€˜&S `ËcbãP-`'æÓ€ø™{[”! ‘ eˆDh;¬ÏVŠEX0fKÄ9Ø-0IWfH°·ÀÎ ² 0Qˆ…){`È##x„™FòW<ñ+®ç*x™²<¹$9E[-qWW.(ÎI+6aaš@.Ây™24àóÌ ‘àƒóýxήÎÎ6޶_-ê¿ÿ"bbãþåÏ«p@át~Ñþ,/³€;€mþ¢%îh^  u÷‹f²@µ éÚWópø~<ß5°j>{‘-¨]cöK'XtÀâ÷ò»oÁÔ(€hƒáÏwÿï?ýG %€fI’q^D$.Tʳ?ÇD *°AôÁ,ÀÁÜÁ ü`6„B$ÄÂBB d€r`)¬‚B(†Í°*`/Ô@4ÀQh†“p.ÂU¸=púažÁ(¼ AÈa!ÚˆbŠX#Ž™…ø!ÁH‹$ ɈQ"K‘5H1RŠT UHò=r9‡\Fº‘;È2‚ü†¼G1”²Q=Ô µC¹¨7„F¢ Ðdt1š ›Ðr´=Œ6¡çЫhÚ>CÇ0Àè3Äl0.ÆÃB±8, “c˱"¬ «Æ°V¬»‰õcϱwEÀ 6wB aAHXLXNØH¨ $4Ú 7 „QÂ'"“¨K´&ºùÄb21‡XH,#Ö/{ˆCÄ7$‰C2'¹I±¤TÒÒFÒnR#é,©›4H#“ÉÚdk²9”, +È…ääÃä3ää!ò[ b@q¤øSâ(RÊjJåå4åe˜2AU£šRݨ¡T5ZB­¡¶R¯Q‡¨4uš9̓IK¥­¢•Óhh÷i¯ètºÝ•N—ÐWÒËéGè—èôw †ƒÇˆg(›gw¯˜L¦Ó‹ÇT071ë˜ç™™oUX*¶*|‘Ê •J•&•*/T©ª¦ªÞª UóUËT©^S}®FU3Sã© Ô–«UªPëSSg©;¨‡ªg¨oT?¤~Yý‰YÃLÃOC¤Q ±_ã¼Æ c³x,!k «†u5Ä&±ÍÙ|v*»˜ý»‹=ª©¡9C3J3W³Ró”f?ã˜qøœtN ç(§—ó~ŠÞï)â)¦4L¹1e\kª–—–X«H«Q«Gë½6®í§¦½E»YûAÇJ'\'GgÎçSÙSݧ §M=:õ®.ªk¥¡»Dw¿n§î˜ž¾^€žLo§Þy½çú}/ýTýmú§õG X³ $Û Î<Å5qo</ÇÛñQC]Ã@C¥a•a—á„‘¹Ñ<£ÕFFŒiÆ\ã$ãmÆmÆ£&&!&KMêMîšRM¹¦)¦;L;LÇÍÌÍ¢ÍÖ™5›=1×2ç›ç›×›ß·`ZxZ,¶¨¶¸eI²äZ¦Yî¶¼n…Z9Y¥XUZ]³F­­%Ö»­»§§¹N“N«žÖgðñ¶É¶©·°åØÛ®¶m¶}agbg·Å®Ã“}º}ý= ‡Ù«Z~s´r:V:ޚΜî?}Åô–é/gXÏÏØ3ã¶Ë)ÄiS›ÓGgg¹sƒóˆ‹‰K‚Ë.—>.›ÆÝȽäJtõq]ázÒõ›³›Âí¨Û¯î6îiî‡ÜŸÌ4Ÿ)žY3sÐÃÈCàQåÑ? Ÿ•0k߬~OCOgµç#/c/‘W­×°·¥wª÷aï>ö>rŸã>ã<7Þ2ÞY_Ì7À·È·ËOÃož_…ßC#ÿdÿzÿѧ€%g‰A[ûøz|!¿Ž?:Ûeö²ÙíAŒ ¹AA‚­‚åÁ­!hÈì­!÷ç˜Î‘Îi…P~èÖÐaæa‹Ã~ '…‡…W†?ŽpˆXÑ1—5wÑÜCsßDúD–DÞ›g1O9¯-J5*>ª.j<Ú7º4º?Æ.fYÌÕXXIlK9.*®6nl¾ßüíó‡ââ ã{˜/È]py¡ÎÂô…§©.,:–@LˆN8”ðA*¨Œ%òw%Ž yÂÂg"/Ñ6шØC\*NòH*Mz’쑼5y$Å3¥,幄'©¼L LÝ›:žšv m2=:½1ƒ’‘qBª!M“¶gêgæfvˬe…²þÅn‹·/•Ék³¬Y- ¶B¦èTZ(×*²geWf¿Í‰Ê9–«ž+Íí̳ÊÛ7œïŸÿíÂá’¶¥†KW-X潬j9²‰Š®Û—Ø(Üxå‡oÊ¿™Ü”´©«Ä¹dÏfÒféæÞ-ž[–ª—æ—n ÙÚ´ ßV´íõöEÛ/—Í(Û»ƒ¶C¹£¿<¸¼e§ÉÎÍ;?T¤TôTúT6îÒݵa×ønÑî{¼ö4ìÕÛ[¼÷ý>ɾÛUUMÕfÕeûIû³÷?®‰ªéø–ûm]­NmqíÇÒý#¶×¹ÔÕÒ=TRÖ+ëGǾþïw- 6 UœÆâ#pDyäé÷ ß÷ :ÚvŒ{¬áÓvg/jBšòšF›Sšû[b[ºOÌ>ÑÖêÞzüGÛœ499â?rýéü§CÏdÏ&žþ¢þË®/~øÕë×Îјѡ—ò—“¿m|¥ýêÀë¯ÛÆÂƾÉx31^ôVûíÁwÜwï£ßOä| (ÿhù±õSЧû“““ÿ˜óüc3-ÛgAMA±Ž|ûQ“ cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅF‡IDATxÚdÌÿO”uð÷çóy¾Üs÷ÜwÜ7Ndf‚¡KnŶØ"jŽVêš1›eËÍå˜Q™¬1)YÌ¢ ‡´ÑVm©É3—5ÓTÊXCWgŒ€R´ƒørÜÜÝsÏó|>ýÒ}yý/Òöl-t]ÇÙ³”éÅŒc*e¦~øú«ËP C’u¸¼~À­±¤fJÌ"³œÂÃ;›!%°Œ<U²nµê³îCç£k×ÜBÓŽ:c”½ûÉW ÓX†ed lö½¤,¤ {rg³MALÛF̓÷q‡ƒ•ö}xÂíPÝØ»½á·Äl2¹ox`sg&ÂÂüèz³E/þ¹bq ­»ªQ¶y EÅþW;‹ CÞ=k‹|w~o+Ðe¤ó…«K ÉÊ™üR¼Õ§«Íãw:­*ú`ð°%ä6l ÞÝòL­<ðÑÛøØË¿µUŠá+ÅSÕëEHc" ŠÃWŠîMž/¼ þÚ²BÅ+ãÁ9H©‹©qîSŽ{kJÜÑÏߩ߇Ÿ/°¸!£obzû‡±{Möx3МDJøÞJÓ“ »V,¾ˆG›ÐóúnooBÿ*rEœ®]…ÅÜóA±´U#ËÛ<¢ÑAÄ‘8XW¹±ê.Î!]ú.Ý-ßœKoد‰ ]Æv, N@ – ¼·^ «¦A)ñ=Y^@/^xd#è¹oÇžú>vzô×Ûˆªfr1¿ 0Èå3OÀ'€³< ‹sú“)bª³fðê8‘ö>ýЀ/R4 ‡‹ÁmŸÉÂï –EaåÓ$B ENp+61ÿ¾1ŸÜÄm„i˱3XçÔPêPÀ)éIe ^ù.Ũ(q²,äu)ÑK žOòŽ¥ôÒ5‡Ëƒÿ`dR`’*I¸_W+ò5& Ý6Ä”Ìpi>—S&áŸþ€m[ „P‚ú€V˜V©6>“MÎYvš‚ÿúkãQ‡RöèWIEND®B`‚apptools-4.1.0/apptools/logger/plugin/view/images/warning.png0000644000175100001440000000676711674464005025375 0ustar ischnellusers00000000000000‰PNG  IHDRóÿa pHYs  šœ OiCCPPhotoshop ICC profilexÚSgTSé=÷ÞôBKˆ€”KoR RB‹€‘&*! Jˆ!¡ÙQÁEEÈ ˆŽŽ€ŒQ, Š Øä!¢Žƒ£ˆŠÊûá{£kÖ¼÷æÍþµ×>ç¬ó³ÏÀ –H3Q5€ ©BàƒÇÄÆáä.@ $p³d!sý#ø~<<+"À¾xÓ ÀM›À0‡ÿêB™\€„Àt‘8K€@zŽB¦@F€˜&S `ËcbãP-`'æÓ€ø™{[”! ‘ eˆDh;¬ÏVŠEX0fKÄ9Ø-0IWfH°·ÀÎ ² 0Qˆ…){`È##x„™FòW<ñ+®ç*x™²<¹$9E[-qWW.(ÎI+6aaš@.Ây™24àóÌ ‘àƒóýxήÎÎ6޶_-ê¿ÿ"bbãþåÏ«p@át~Ñþ,/³€;€mþ¢%îh^  u÷‹f²@µ éÚWópø~<ß5°j>{‘-¨]cöK'XtÀâ÷ò»oÁÔ(€hƒáÏwÿï?ýG %€fI’q^D$.Tʳ?ÇD *°AôÁ,ÀÁÜÁ ü`6„B$ÄÂBB d€r`)¬‚B(†Í°*`/Ô@4ÀQh†“p.ÂU¸=púažÁ(¼ AÈa!ÚˆbŠX#Ž™…ø!ÁH‹$ ɈQ"K‘5H1RŠT UHò=r9‡\Fº‘;È2‚ü†¼G1”²Q=Ô µC¹¨7„F¢ Ðdt1š ›Ðr´=Œ6¡çЫhÚ>CÇ0Àè3Äl0.ÆÃB±8, “c˱"¬ «Æ°V¬»‰õcϱwEÀ 6wB aAHXLXNØH¨ $4Ú 7 „QÂ'"“¨K´&ºùÄb21‡XH,#Ö/{ˆCÄ7$‰C2'¹I±¤TÒÒFÒnR#é,©›4H#“ÉÚdk²9”, +È…ääÃä3ää!ò[ b@q¤øSâ(RÊjJåå4åe˜2AU£šRݨ¡T5ZB­¡¶R¯Q‡¨4uš9̓IK¥­¢•Óhh÷i¯ètºÝ•N—ÐWÒËéGè—èôw †ƒÇˆg(›gw¯˜L¦Ó‹ÇT071ë˜ç™™oUX*¶*|‘Ê •J•&•*/T©ª¦ªÞª UóUËT©^S}®FU3Sã© Ô–«UªPëSSg©;¨‡ªg¨oT?¤~Yý‰YÃLÃOC¤Q ±_ã¼Æ c³x,!k «†u5Ä&±ÍÙ|v*»˜ý»‹=ª©¡9C3J3W³Ró”f?ã˜qøœtN ç(§—ó~ŠÞï)â)¦4L¹1e\kª–—–X«H«Q«Gë½6®í§¦½E»YûAÇJ'\'GgÎçSÙSݧ §M=:õ®.ªk¥¡»Dw¿n§î˜ž¾^€žLo§Þy½çú}/ýTýmú§õG X³ $Û Î<Å5qo</ÇÛñQC]Ã@C¥a•a—á„‘¹Ñ<£ÕFFŒiÆ\ã$ãmÆmÆ£&&!&KMêMîšRM¹¦)¦;L;LÇÍÌÍ¢ÍÖ™5›=1×2ç›ç›×›ß·`ZxZ,¶¨¶¸eI²äZ¦Yî¶¼n…Z9Y¥XUZ]³F­­%Ö»­»§§¹N“N«žÖgðñ¶É¶©·°åØÛ®¶m¶}agbg·Å®Ã“}º}ý= ‡Ù«Z~s´r:V:ޚΜî?}Åô–é/gXÏÏØ3ã¶Ë)ÄiS›ÓGgg¹sƒóˆ‹‰K‚Ë.—>.›ÆÝȽäJtõq]ázÒõ›³›Âí¨Û¯î6îiî‡ÜŸÌ4Ÿ)žY3sÐÃÈCàQåÑ? Ÿ•0k߬~OCOgµç#/c/‘W­×°·¥wª÷aï>ö>rŸã>ã<7Þ2ÞY_Ì7À·È·ËOÃož_…ßC#ÿdÿzÿѧ€%g‰A[ûøz|!¿Ž?:Ûeö²ÙíAŒ ¹AA‚­‚åÁ­!hÈì­!÷ç˜Î‘Îi…P~èÖÐaæa‹Ã~ '…‡…W†?ŽpˆXÑ1—5wÑÜCsßDúD–DÞ›g1O9¯-J5*>ª.j<Ú7º4º?Æ.fYÌÕXXIlK9.*®6nl¾ßüíó‡ââ ã{˜/È]py¡ÎÂô…§©.,:–@LˆN8”ðA*¨Œ%òw%Ž yÂÂg"/Ñ6шØC\*NòH*Mz’쑼5y$Å3¥,幄'©¼L LÝ›:žšv m2=:½1ƒ’‘qBª!M“¶gêgæfvˬe…²þÅn‹·/•Ék³¬Y- ¶B¦èTZ(×*²geWf¿Í‰Ê9–«ž+Íí̳ÊÛ7œïŸÿíÂá’¶¥†KW-X潬j9²‰Š®Û—Ø(Üxå‡oÊ¿™Ü”´©«Ä¹dÏfÒféæÞ-ž[–ª—æ—n ÙÚ´ ßV´íõöEÛ/—Í(Û»ƒ¶C¹£¿<¸¼e§ÉÎÍ;?T¤TôTúT6îÒݵa×ønÑî{¼ö4ìÕÛ[¼÷ý>ɾÛUUMÕfÕeûIû³÷?®‰ªéø–ûm]­NmqíÇÒý#¶×¹ÔÕÒ=TRÖ+ëGǾþïw- 6 UœÆâ#pDyäé÷ ß÷ :ÚvŒ{¬áÓvg/jBšòšF›Sšû[b[ºOÌ>ÑÖêÞzüGÛœ499â?rýéü§CÏdÏ&žþ¢þË®/~øÕë×Îјѡ—ò—“¿m|¥ýêÀë¯ÛÆÂƾÉx31^ôVûíÁwÜwï£ßOä| (ÿhù±õSЧû“““ÿ˜óüc3-ÛgAMA±Ž|ûQ“ cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅFIDATxÚlÒKh\uÇñïùßÇLn&™I2 y™¤Æ„šÚ8ØšÒ`µ…*öÕÄB¥èÎEAЕº ‚Š›€bÓhA¨+¡.ÜøFJ•4µµ±Óh3Iiœt^÷þïq1)nüíÏçÀ9?QUîņ–‹³Ÿâ‹Æ–…ÙãƒQ=´#‡O¶;ÆÿEÞî‡XχTÖÁõ=ŠVéW½×+µÏÞº4j ±*`Àõ`dßž>›h¥D!”n[llÉ¦Ž êíÇ®ÓÿÅ•üòÍÚ¬›€87`6–ãøi²C¢þΩܻ¬Ü„j‘]Ïlz?¬âƒ* ^ÐÜÄÔÊ1+‹ÿpëê½cÙW†÷w§VÎþHÝ£ôné ¬k°czüê…Ï1Sg)=}~ù޽S}'Â*ÉØ*j\?¹¨”Bþ¾²JáÒ*ƒÛºN?øK”ö‰<é‰ç¨‡®C610¼©"8®ƒ›L4€d³OÿX–Áñ,QHÛcùÍ/Æ?ÿ€Úÿ^u{ ˜»Æ¾ÝÎñ0$-ÆE\¯TÖk.Yüu•‘‰ž3ý¹»rëF–ÖÆ°ÕoA y¤ì<°Åûpy±ˆsˆj`k`#Ý3ÕódüÛ5Ú†Rt–6ŠtðGĽÞó ÏÇ4§]¢Ù:™ÍLV$\ ~*à6Añ¨P<*¤¶ƒÀ4m«ËÃãæã¯^­ØJDK»»gw¾#§çæI|¿­@7df”¶…ÐÒh! ûú»Œ/;¿>ù¦ám}ÈýÄ[˜GæcØ d› ¼;¿ãV– ´¶@³¿À°N.'3ßœxÙÈÜãæ…gwĹ>zè » Ö®6Ñ9+×aùÜÁ ¼çqÛÒááSaµfq1I‘dBq›DÚR F£ú]‘ÎÚZUªh\¶Hù²ö Ÿº_ÎLò„c%/‘–]#â jiô@TPâX1±­ ÅHݪâÑT.ñå¿Ý5µªß:IEND®B`‚apptools-4.1.0/apptools/logger/plugin/view/images/crit_error.png0000644000175100001440000000156711674464005026073 0ustar ischnellusers00000000000000‰PNG  IHDRóÿabKGDÿÿÿ ½§“ pHYs  šœtIMEØ&ãEï+IDAT8Ëm“Kh\e†ŸïÿÏÜǘԤ™6L  T¡jË(šD‘ºÚMW"Š 7-–ØT„6*¨©D”‚BA\(¢ ¨ AŠ©([)ôjÒŽNB’“Î%s9çüçw‘RÚwù}ðÀwyÄŸùø­\<ÑÖËR«.ϘÈn¬ÉFÀG™©}ÏšuBñÕ¦h¬5ˆh<·j¿ùüåÜÁ7ÿ¾ ðáÑôÔsûOd—ŠW°6@)¥ux`|´¡­½×~÷å¡Ü«o†×'ÞØ2õìÞ‰liáÍÆM‘$‘H×­ã¹uD‰d'mûã·ÇrcïÌëxó“Ü螃nüF­²Hßà“ í|‰¹üj•š2áH’‡Ÿ8Œç.3_ø“ÆJIر'óÅɽ)¥Tÿ_—~ææÒ,›º¸ç+$ÛØ5r E(çÑ‘£$îîcè‘$îêb¹”'?}F´vúc|S©”¸qíW6Ÿ?Å}¾H4ÑÍÓûN t€‹gO2›?G` ¶^Æß8ÖZê+DW7ÈýôÍúÛz¥Bxn…‹g?ãß¿ÂÚ€ 0Xk±ÖâXk©ÕÊ(¥pœ"ÂÌ•Óô>ƒˆF‰ÂóêL_>Mµ²„ˆàûA`­E‰ŽB¯l[z;»†ÇðZUŒßÄóê¿ÉîÑ16w÷òÿÙEA)¥u4' “éb÷È!ÜVã7¹vák®_þã71~“ìSclí¹Ç Æåãk¥” G¢ÚV«L4ÖAáú/\8÷Ji´ÓµuõÚ"ÖZ¢±¾ç¶‚ ˆŠ1>“ã³÷tnIiRí)R=CÌ\BDVçTšþÁ,sÿœ§Z.âùn«Tœ+9þozý•'Ç;gS©L*žhWÆx„Â1DdÍ‹ç6pœ+µ¥Öü|¾8>YJß&Óñ#]³=ÛS‰ä&%¢nXP«”ܹ¹éÅÃïÓw´àý×» ±XbeuËj P¯×b¯½»p‹êÿdñV®AFIEND®B`‚apptools-4.1.0/apptools/logger/plugin/view/images/image_LICENSE.txt0000644000175100001440000000111311674464005026163 0ustar ischnellusers00000000000000The icons are mostly derived work from other icons. As such they are licensed accordingly to the original license: Crystal Project: LGPL license as described in image_LICENSE_CP.txt Unless stated in this file, icons are work of enthought, and are released under BSD-like license. Files and orginal authors: ---------------------------------------------------------------- about.png Crystal Project crit_error.png Crystal Project debug.png Crystal Project error.png Crystal Project warning.png Crystal Project apptools-4.1.0/apptools/logger/plugin/view/images/info.png0000644000175100001440000000706011674464005024646 0ustar ischnellusers00000000000000‰PNG  IHDRóÿa pHYs  šœ OiCCPPhotoshop ICC profilexÚSgTSé=÷ÞôBKˆ€”KoR RB‹€‘&*! Jˆ!¡ÙQÁEEÈ ˆŽŽ€ŒQ, Š Øä!¢Žƒ£ˆŠÊûá{£kÖ¼÷æÍþµ×>ç¬ó³ÏÀ –H3Q5€ ©BàƒÇÄÆáä.@ $p³d!sý#ø~<<+"À¾xÓ ÀM›À0‡ÿêB™\€„Àt‘8K€@zŽB¦@F€˜&S `ËcbãP-`'æÓ€ø™{[”! ‘ eˆDh;¬ÏVŠEX0fKÄ9Ø-0IWfH°·ÀÎ ² 0Qˆ…){`È##x„™FòW<ñ+®ç*x™²<¹$9E[-qWW.(ÎI+6aaš@.Ây™24àóÌ ‘àƒóýxήÎÎ6޶_-ê¿ÿ"bbãþåÏ«p@át~Ñþ,/³€;€mþ¢%îh^  u÷‹f²@µ éÚWópø~<ß5°j>{‘-¨]cöK'XtÀâ÷ò»oÁÔ(€hƒáÏwÿï?ýG %€fI’q^D$.Tʳ?ÇD *°AôÁ,ÀÁÜÁ ü`6„B$ÄÂBB d€r`)¬‚B(†Í°*`/Ô@4ÀQh†“p.ÂU¸=púažÁ(¼ AÈa!ÚˆbŠX#Ž™…ø!ÁH‹$ ɈQ"K‘5H1RŠT UHò=r9‡\Fº‘;È2‚ü†¼G1”²Q=Ô µC¹¨7„F¢ Ðdt1š ›Ðr´=Œ6¡çЫhÚ>CÇ0Àè3Äl0.ÆÃB±8, “c˱"¬ «Æ°V¬»‰õcϱwEÀ 6wB aAHXLXNØH¨ $4Ú 7 „QÂ'"“¨K´&ºùÄb21‡XH,#Ö/{ˆCÄ7$‰C2'¹I±¤TÒÒFÒnR#é,©›4H#“ÉÚdk²9”, +È…ääÃä3ää!ò[ b@q¤øSâ(RÊjJåå4åe˜2AU£šRݨ¡T5ZB­¡¶R¯Q‡¨4uš9̓IK¥­¢•Óhh÷i¯ètºÝ•N—ÐWÒËéGè—èôw †ƒÇˆg(›gw¯˜L¦Ó‹ÇT071ë˜ç™™oUX*¶*|‘Ê •J•&•*/T©ª¦ªÞª UóUËT©^S}®FU3Sã© Ô–«UªPëSSg©;¨‡ªg¨oT?¤~Yý‰YÃLÃOC¤Q ±_ã¼Æ c³x,!k «†u5Ä&±ÍÙ|v*»˜ý»‹=ª©¡9C3J3W³Ró”f?ã˜qøœtN ç(§—ó~ŠÞï)â)¦4L¹1e\kª–—–X«H«Q«Gë½6®í§¦½E»YûAÇJ'\'GgÎçSÙSݧ §M=:õ®.ªk¥¡»Dw¿n§î˜ž¾^€žLo§Þy½çú}/ýTýmú§õG X³ $Û Î<Å5qo</ÇÛñQC]Ã@C¥a•a—á„‘¹Ñ<£ÕFFŒiÆ\ã$ãmÆmÆ£&&!&KMêMîšRM¹¦)¦;L;LÇÍÌÍ¢ÍÖ™5›=1×2ç›ç›×›ß·`ZxZ,¶¨¶¸eI²äZ¦Yî¶¼n…Z9Y¥XUZ]³F­­%Ö»­»§§¹N“N«žÖgðñ¶É¶©·°åØÛ®¶m¶}agbg·Å®Ã“}º}ý= ‡Ù«Z~s´r:V:ޚΜî?}Åô–é/gXÏÏØ3ã¶Ë)ÄiS›ÓGgg¹sƒóˆ‹‰K‚Ë.—>.›ÆÝȽäJtõq]ázÒõ›³›Âí¨Û¯î6îiî‡ÜŸÌ4Ÿ)žY3sÐÃÈCàQåÑ? Ÿ•0k߬~OCOgµç#/c/‘W­×°·¥wª÷aï>ö>rŸã>ã<7Þ2ÞY_Ì7À·È·ËOÃož_…ßC#ÿdÿzÿѧ€%g‰A[ûøz|!¿Ž?:Ûeö²ÙíAŒ ¹AA‚­‚åÁ­!hÈì­!÷ç˜Î‘Îi…P~èÖÐaæa‹Ã~ '…‡…W†?ŽpˆXÑ1—5wÑÜCsßDúD–DÞ›g1O9¯-J5*>ª.j<Ú7º4º?Æ.fYÌÕXXIlK9.*®6nl¾ßüíó‡ââ ã{˜/È]py¡ÎÂô…§©.,:–@LˆN8”ðA*¨Œ%òw%Ž yÂÂg"/Ñ6шØC\*NòH*Mz’쑼5y$Å3¥,幄'©¼L LÝ›:žšv m2=:½1ƒ’‘qBª!M“¶gêgæfvˬe…²þÅn‹·/•Ék³¬Y- ¶B¦èTZ(×*²geWf¿Í‰Ê9–«ž+Íí̳ÊÛ7œïŸÿíÂá’¶¥†KW-X潬j9²‰Š®Û—Ø(Üxå‡oÊ¿™Ü”´©«Ä¹dÏfÒféæÞ-ž[–ª—æ—n ÙÚ´ ßV´íõöEÛ/—Í(Û»ƒ¶C¹£¿<¸¼e§ÉÎÍ;?T¤TôTúT6îÒݵa×ønÑî{¼ö4ìÕÛ[¼÷ý>ɾÛUUMÕfÕeûIû³÷?®‰ªéø–ûm]­NmqíÇÒý#¶×¹ÔÕÒ=TRÖ+ëGǾþïw- 6 UœÆâ#pDyäé÷ ß÷ :ÚvŒ{¬áÓvg/jBšòšF›Sšû[b[ºOÌ>ÑÖêÞzüGÛœ499â?rýéü§CÏdÏ&žþ¢þË®/~øÕë×Îјѡ—ò—“¿m|¥ýêÀë¯ÛÆÂƾÉx31^ôVûíÁwÜwï£ßOä| (ÿhù±õSЧû“““ÿ˜óüc3-ÛgAMA±Ž|ûQ“ cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅFKIDATxÚd“ÍOeÆï;ïìììÌÒý€åc Áµ¢A1 ¥1BXMÚg=·ü jÔ›ÆÄƒ›kL*š¦ RPk -t—»0ì²ììÌ·‡ãïþ<É“F&“0{‹ç”´Í¡½­û4«É®< ]ÇõBJƒn~ú–©¸r}‘¥ßw±M ŒðÚz'BZæBЏY¾ƒ³ù€Dþ,*ÕM¦h§‰â˜A³Q§QÛÆ7 ð›x2IÛÀ2K¨ fÓÌã #------------------------------------------------------------------------------ import logging from apptools.preferences.ui.api import PreferencesPage from traits.api import Bool, Trait, Str from traitsui.api import EnumEditor, Group, Item, View class LoggerPreferencesPage(PreferencesPage): """ A preference page for the logger plugin. """ #### 'PreferencesPage' interface ########################################## # The page's category (e.g. 'General/Appearance'). The empty string means # that this is a top-level page. category = '' # The page's help identifier (optional). If a help Id *is* provided then # there will be a 'Help' button shown on the preference page. help_id = '' # The page name (this is what is shown in the preferences dialog. name = 'Logger' # The path to the preferences node that contains the preferences. preferences_path = 'apptools.logger' #### Preferences ########################################################### # The log levels level = Trait('Info', {'Debug' : logging.DEBUG, 'Info' : logging.INFO, 'Warning' : logging.WARNING, 'Error' : logging.ERROR, 'Critical' : logging.CRITICAL, }, is_str = True, ) enable_agent = Bool(False) smtp_server = Str to_address = Str from_address = Str # The view used to change the plugin preferences traits_view = View( Group( Group( Item( name='level', editor=EnumEditor( values={ 'Debug' : '1:Debug', 'Info' : '2:Info', 'Warning' : '3:Warning', 'Error' : '4:Error' , 'Critical' : '5:Critical', }, ), style='simple', ), label='Logger Settings', show_border=True, ), Group(Item(name='10')), Group( Group( Group(Item(name='enable_agent', label='Enable quality agent'), show_left=False), Group(Item(name='smtp_server', label='SMTP server'), Item(name='from_address'), Item(name='to_address'), enabled_when='enable_agent==True')), label='Quality Agent Settings', show_border=True, ), ), ) #### EOF ###################################################################### apptools-4.1.0/apptools/logger/plugin/view/logger_view.py0000644000175100001440000001560311674464005024625 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ # Standard library imports from datetime import datetime import logging # Enthought library imports. from pyface.api import ImageResource, clipboard from pyface.workbench.api import TraitsUIView from traits.api import Button, Instance, List, Property, Str, \ cached_property, on_trait_change from traitsui.api import View, Group, Item, CodeEditor, \ TabularEditor, spring from traitsui.tabular_adapter import TabularAdapter # Local imports from apptools.logger.agent.quality_agent_view import QualityAgentView from apptools.logger.plugin import view from apptools.logger.plugin.logger_service import LoggerService # Constants _IMAGE_MAP = { logging.DEBUG: ImageResource('debug'), logging.INFO: ImageResource('info'), logging.WARNING: ImageResource('warning'), logging.ERROR: ImageResource('error'), logging.CRITICAL: ImageResource('crit_error') } class LogRecordAdapter(TabularAdapter): """ A TabularEditor adapter for logging.LogRecord objects. """ columns = [ ('Level', 'level'), ('Date', 'date'), ('Time', 'time'), ('Message', 'message') ] column_widths = [ 80, 100, 120, -1 ] level_image = Property level_text = Property(Str) date_text = Property(Str) time_text = Property(Str) message_text = Property(Str) def get_width(self, object, trait, column): return self.column_widths[column] def _get_level_image(self): return _IMAGE_MAP[self.item.levelno] def _get_level_text(self): return self.item.levelname.capitalize() def _get_date_text(self): dt = datetime.fromtimestamp(self.item.created) return dt.date().isoformat() def _get_time_text(self): dt = datetime.fromtimestamp(self.item.created) return dt.time().isoformat() def _get_message_text(self): # Just display the first line of multiline messages, like stacktraces. msg = self.item.getMessage() msgs = msg.strip().split('\n') if len(msgs) > 1: suffix = '... [double click for details]' else: suffix = '' abbrev_msg = msgs[0] + suffix return abbrev_msg class LoggerView(TraitsUIView): """ The Workbench View showing the list of log items. """ id = Str('apptools.logger.plugin.view.logger_view.LoggerView') name = Str('Logger') service = Instance(LoggerService) log_records = List(Instance(logging.LogRecord)) formatted_records = Property(Str, depends_on='log_records') activated = Instance(logging.LogRecord) activated_text = Property(Str, depends_on='activated') reset_button = Button("Reset Logs") show_button = Button("Complete Text Log") copy_button = Button("Copy Log to Clipboard") code_editor = CodeEditor(lexer='null', show_line_numbers=False) log_records_editor = TabularEditor(adapter=LogRecordAdapter(), editable=False, activated='activated') trait_view = View(Group(Item('log_records', editor=log_records_editor), Group(Item('reset_button'), spring, Item('show_button'), Item('copy_button'), orientation='horizontal', show_labels=False), show_labels=False)) ########################################################################### # LogQueueHandler view interface ########################################################################### def update(self, force=False): """ Update 'log_records' if our handler has new records or 'force' is set. """ service = self.service if service.handler.has_new_records() or force: log_records = [ rec for rec in service.handler.get() if rec.levelno >= service.preferences.level_ ] log_records.reverse() self.log_records = log_records ########################################################################### # Private interface ########################################################################### @on_trait_change('service.preferences.level_') def _update_log_records(self): self.service.handler._view = self self.update(force=True) def _reset_button_fired(self): self.service.handler.reset() self.log_records = [] def _show_button_fired(self): self.edit_traits(view=View(Item('formatted_records', editor=self.code_editor, style='readonly', show_label=False), width=800, height=600, resizable=True, buttons=[ 'OK' ], title='Complete Text Log')) def _copy_button_fired(self): clipboard.text_data = self.formatted_records @cached_property def _get_formatted_records(self): return '\n'.join([ self.service.handler.formatter.format(record) for record in self.log_records ]) def _activated_changed(self): if self.activated is None: return msg = self.activated.getMessage() if self.service.preferences.enable_agent: dialog = QualityAgentView(msg=msg, service=self.service) dialog.open() else: self.edit_traits(view=View(Item('activated_text', editor=self.code_editor, style='readonly', show_label=False), width=800, height=600, resizable=True, buttons=[ 'OK' ], title='Log Message Detail')) @cached_property def _get_activated_text(self): if self.activated is None: return '' else: return self.activated.getMessage() #### EOF ###################################################################### apptools-4.1.0/apptools/logger/plugin/view/__init__.py0000644000175100001440000000000011674464005024034 0ustar ischnellusers00000000000000apptools-4.1.0/apptools/logger/log_queue_handler.py0000644000175100001440000000445611674464005023532 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ # Standard library imports. from logging import Handler # Local imports. from ring_buffer import RingBuffer class LogQueueHandler(Handler): """ Buffers up the log messages so that we can display them later. This is important on startup when log messages are generated before the ui has started. By putting them in this queue we can display them once the ui is ready. """ # The view where updates will go _view = None def __init__(self, size=1000): Handler.__init__(self) # only buffer 1000 log records self.size = size self.ring = RingBuffer(self.size) self.dirty = False return def emit(self, record): """ Actually this is more like an enqueue than an emit().""" self.ring.append(record) if self._view is not None: try: self._view.update() except Exception, e: pass self.dirty = True return def get(self): self.dirty = False try: result = self.ring.get() except Exception, msg: # we did our best and it won't cause too much damage # to just return a bogus message result = [] return result def has_new_records(self): return self.dirty def reset(self): # start over with a new empty buffer self.ring = RingBuffer(self.size) if self._view is not None: try: self._view.update() except Exception, e: pass self.dirty = True return ## EOF ################################################################## apptools-4.1.0/apptools/logger/logger.py0000644000175100001440000000545211674464005021324 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Convenience functions for creating logging handlers etc. """ # Standard library imports. import logging from logging.handlers import RotatingFileHandler # Enthought library imports. from traits.util.api import deprecated # Local imports. from log_queue_handler import LogQueueHandler # The default logging level. LEVEL = logging.DEBUG # The default formatter. FORMATTER = logging.Formatter('%(levelname)s|%(asctime)s|%(message)s') class LogFileHandler(RotatingFileHandler): """ The default log file handler. """ def __init__(self, path, maxBytes=1000000, backupCount=3, level=None, formatter=None): RotatingFileHandler.__init__( self, path, maxBytes=maxBytes, backupCount=3 ) if level is None: level = LEVEL if formatter is None: formatter = FORMATTER # Set our default formatter and log level. self.setFormatter(formatter) self.setLevel(level) @deprecated('use "LogFileHandler"') def create_log_file_handler(path, maxBytes=1000000, backupCount=3, level=None, formatter=None): """ Creates a log file handler. This is just a convenience function to make it easy to create the same kind of handlers across applications. It sets the handler's formatter to the default formatter, and its logging level to the default logging level. """ if level is None: level = LEVEL if formatter is None: formatter = FORMATTER handler = RotatingFileHandler( path, maxBytes=maxBytes, backupCount=backupCount ) handler.setFormatter(formatter) handler.setLevel(level) return handler def add_log_queue_handler(logger, level=None, formatter=None): """ Adds a queueing log handler to a logger. """ if level is None: level = LEVEL if formatter is None: formatter = FORMATTER # Add the handler to the root logger. log_queue_handler = LogQueueHandler() log_queue_handler.setLevel(level) log_queue_handler.setFormatter(formatter) logger.addHandler(log_queue_handler) return log_queue_handler #### EOF ###################################################################### apptools-4.1.0/apptools/logger/filtering_handler.py0000644000175100001440000001367111674464005023527 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A log handler that allows filtering of messages by origin. """ # Standard library imports. import logging, inspect, os # Local imports. # # fixme: This module was just copied over from 'envisage.core' (so # that we don't rely on Envisage here!). Where should this module go? from util import get_module_name class FilteringHandler(logging.Handler): """ A log handler that allows filtering of messages by origin. Example ------- :: from apptools.logger.api import DebugHandler, logger handler = FilteringHandler( include = { 'envisage.core' : True }, exclude = { 'envisage.core.application' : False } ) logger.addHandler(handler) Notes ----- The boolean value specified for each name in the include and exclude dictionaries indicates whether or not the include or exclude should pertain to any sub-packages and modules. The above example includes all log messages from anything contained in, or under the 'envisage.core' package, EXCEPT for any log messages from the 'envisage.core.application' module. """ ########################################################################### # 'object' interface. ########################################################################### def __init__(self, include=None, exclude=None): """ Creates a new handler. """ # Base class constructor. logging.Handler.__init__(self) # Packages or modules to include. self.include = include is not None and include or {} # Packages or modules to exclude. self.exclude = exclude is not None and exclude or {} return ########################################################################### # 'Handler' interface. ########################################################################### def emit(self, record): """ Emits a log record. """ # Get the name of the module that the logger was called from. module_name = self._get_module_name() if len(self.include) == 0 or self._include(module_name): if len(self.exclude) == 0 or not self._exclude(module_name): self.filtered_emit(record) return ########################################################################### # 'Handler' interface. ########################################################################### def filtered_emit(self, record): """ Emits a log record if it has not been filtered. """ print record.getMessage() return ########################################################################### # Private interface. ########################################################################### def _get_module_name(self): """ Returns the module that the logger was actually called from. """ # fixem: Ahem.... what can I say... this gets us to the actual caller! frame = inspect.currentframe() frame = frame.f_back.f_back.f_back.f_back.f_back.f_back.f_back # This returns a tuple containing the last 5 elements of the frame # record which are:- # # - the filename # - the line number # - the function name # - the list of lines of context from the source code # - the index of the current line within that list filename, lineno, funcname, source, index = inspect.getframeinfo(frame) # The plugin definition's location is the directory containing the # module that it is defined in. self.location = os.path.dirname(filename) # We can't use 'inspect.getmodulename' here as it gets confused because # of our import hook in Envisage 8^( # # e.g. for the core plugin definition:- # # using inspect -> 'core_plugin_definition' # using ours -> 'envisage.core.core_plugin_definition' return get_module_name(filename) def _include(self, module_name): """ Is the module name in the include set? """ for item, include_children in self.include.items(): if item == module_name: include = True break elif include_children and self._is_child_of(item, module_name): include = True break else: include = False return include def _exclude(self, module_name): """ Is the module name in the exclude set? """ for item, exclude_children in self.exclude.items(): if item == module_name: exclude = True break elif exclude_children and self._is_child_of(item, module_name): exclude = True break else: exclude = False return exclude def _is_child_of(self, x, y): """ Is 'y' a child symbol of 'x'? e.g. 'foo.bar.baz' (y) is a child of 'foo.bar' (x) """ if y.startswith(x): x_atoms = x.split('.') y_atoms = y.split('.') is_child_of = y_atoms[:len(x_atoms)] == x_atoms else: is_child_of = False return is_child_of #### EOF ###################################################################### apptools-4.1.0/apptools/logger/api.py0000644000175100001440000000035211674464005020610 0ustar ischnellusers00000000000000from logger import add_log_queue_handler, create_log_file_handler from logger import FORMATTER, LEVEL, LogFileHandler from log_point import log_point from filtering_handler import FilteringHandler from null_handler import NullHandler apptools-4.1.0/apptools/logger/custom_excepthook.py0000644000175100001440000000255711674464005023613 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ # Standard library imports. import logging from traceback import format_exception """ To catch exceptions with our own code this code needs to be added sys.excepthook = custom_excepthook """ def custom_excepthook(type, value, traceback): """ Pass on the exception to the logging system. """ msg = 'Custom - Traceback (most recent call last):\n' list = format_exception(type, value, traceback) msg = "".join(list) # Try to find the module that the exception actually came from. name = getattr(traceback.tb_frame, 'f_globals', {}).get('__name__', __name__) logger = logging.getLogger(name) logger.error(msg) return ## EOF ################################################################## apptools-4.1.0/apptools/logger/agent/0000755000175100001440000000000011674464005020563 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/logger/agent/quality_agent_mailer.py0000644000175100001440000000766311674464005025350 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ # Standard library imports. import logging import os # Enthought library imports. from traits.util.home_directory import get_home_directory # Setup a logger for this module. logger = logging.getLogger(__name__) def create_email_message(fromaddr, toaddrs, ccaddrs, subject, priority, include_project=False, stack_trace="", comments=""): # format a message suitable to be sent to the Roundup bug tracker from email.MIMEMultipart import MIMEMultipart from email.MIMEText import MIMEText from email.MIMEBase import MIMEBase message = MIMEMultipart() message['Subject'] = "%s [priority=%s]" % (subject, priority) message['To'] = ', '.join(toaddrs) message['Cc'] = ', '.join(ccaddrs) message['From'] = fromaddr message.preamble = 'You will not see this in a MIME-aware mail reader.\n' message.epilogue = ' ' # To guarantee the message ends with a newline # First section is simple ASCII data ... m = [] m.append("Bug Report") m.append("==============================") m.append("") if len(comments) > 0: m.append("Comments:") m.append("========") m.append(comments) m.append("") if len(stack_trace) > 0: m.append("Stack Trace:") m.append("===========") m.append(stack_trace) m.append("") msg = MIMEText('\n'.join(m)) message.attach(msg) # Include the log file ... if True: try: log = os.path.join(get_home_directory(), 'envisage.log') f = open(log, 'r') entries = f.readlines() f.close() ctype = 'application/octet-stream' maintype, subtype = ctype.split('/', 1) msg = MIMEBase(maintype, subtype) msg = MIMEText(''.join(entries)) msg.add_header('Content-Disposition', 'attachment', filename='logfile.txt') message.attach(msg) except: logger.exception('Failed to include log file with message') # Include the environment variables ... if True: """ Transmit the user's environment settings as well. Main purpose is to work out the user name to help with following up on bug reports and in future we should probably send less data. """ try: entries = [] for key, value in os.environ.iteritems(): entries.append('%30s : %s\n' % (key, value)) ctype = 'application/octet-stream' maintype, subtype = ctype.split('/', 1) msg = MIMEBase(maintype, subtype) msg = MIMEText(''.join(entries)) msg.add_header('Content-Disposition', 'attachment', filename='environment.txt') message.attach(msg) except: logger.exception('Failed to include environment variables with message') # FIXME: no project plugins exist for Envisage 3, yet, and this isn't the right # way to do it, either. See the docstring of attachments.py. # # Attach the project if requested ... # if include_project: # from attachments import Attachments # try: # attachments = Attachments(message) # attachments.package_any_relevant_files() # except: # logger.exception('Failed to include workspace files with message') return message apptools-4.1.0/apptools/logger/agent/__init__.py0000644000175100001440000000004211674464005022670 0ustar ischnellusers00000000000000""" lib.apptools.logger.agent """ apptools-4.1.0/apptools/logger/agent/attachments.py0000644000175100001440000000536511674464005023461 0ustar ischnellusers00000000000000""" Attach relevant project files. FIXME: there are no public project plugins for Envisage 3, yet. In any case, this stuff should not be hard-coded, but extensible via extension points. The code remains here because we can reuse the zip utility code in that extensible rewrite. """ import logging import os.path from email import Encoders from email.MIMEBase import MIMEBase from traits.api import Any, HasTraits logger = logging.getLogger(__name__) class Attachments(HasTraits): application = Any() message = Any() def __init__(self, message, **traits): traits = traits.copy() traits['message'] = message super(Attachments, self).__init__(**traits) # FIXME: all of the package_*() methods refer to deprecated project plugins. def package_workspace(self): if self.application is None: pass workspace = self.application.get_service('envisage.project.IWorkspace') if workspace is not None: dir = workspace.path self._attach_directory(dir) return def package_single_project(self): if self.application is None: pass single_project = self.application.get_service('envisage.single_project.ModelService') if single_project is not None: dir = single_project.location self._attach_directory(dir) def package_any_relevant_files(self): self.package_workspace() self.package_single_project() return def _attach_directory(self, dir): relpath = os.path.basename(dir) import zipfile from cStringIO import StringIO ctype = 'application/octet-stream' maintype, subtype = ctype.split('/', 1) msg = MIMEBase(maintype, subtype) file_object = StringIO() zip = zipfile.ZipFile(file_object, 'w') _append_to_zip_archive(zip, dir, relpath) zip.close() msg.set_payload(file_object.getvalue()) Encoders.encode_base64(msg) # Encode the payload using Base64 msg.add_header('Content-Disposition', 'attachment', filename='project.zip') self.message.attach(msg) file_object.close() def _append_to_zip_archive(zip, dir, relpath): """ Add all files in and below directory dir into zip archive""" for filename in os.listdir(dir): path = os.path.join(dir, filename) if os.path.isfile(path): name = os.path.join(relpath, filename) zip.write(path, name) logger.debug('adding %s to error report' % path) else: if filename != ".svn": # skip svn files if any subdir = os.path.join(dir, filename) _append_to_zip_archive(zip, subdir, os.path.join(relpath, filename)) return apptools-4.1.0/apptools/logger/agent/quality_agent_view.py0000644000175100001440000002772311674464005025050 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ # Standard library imports. import logging # Enthought library imports. from pyface.api import Dialog from traits.api import Any, Str, Tuple # Setup a logger for this module. logger = logging.getLogger(__name__) priority_levels = ['Low', 'Medium', 'High', 'Critical'] class QualityAgentView(Dialog): size = Tuple((700, 900)) title = Str('Quality Agent') # The associated LoggerService. service = Any() msg = Str('') subject = Str('Untitled Error Report') to_address = Str() cc_address = Str('') from_address = Str() smtp_server = Str() priority = Str(priority_levels[2]) comments = Str('None') include_userdata = Any ########################################################################### # Protected 'Dialog' interface. ########################################################################### # fixme: Ideally, this should be passed in; this topic ID belongs to the # Enlib help project/plug-in. help_id = 'enlib|HID_Quality_Agent_Dlg' def _create_dialog_area(self, parent): """ Creates the main content of the dialog. """ import wx parent.SetSizeHints(minW=300, minH=575) # Add the main panel sizer = wx.BoxSizer(wx.VERTICAL) panel = wx.Panel(parent, -1) panel.SetSizer(sizer) panel.SetAutoLayout(True) # Add a descriptive label at the top ... label = wx.StaticText(panel, -1, "Send a comment or bug report ...") sizer.Add(label, 0, wx.ALL, border=5) # Add the stack trace view ... error_panel = self._create_error_panel(panel) sizer.Add(error_panel, 1, wx.ALL|wx.EXPAND|wx.CLIP_CHILDREN, border=5) # Update the layout: sizer.Fit(panel) # Add the error report view ... report_panel = self._create_report_panel(panel) sizer.Add(report_panel, 2, wx.ALL|wx.EXPAND|wx.CLIP_CHILDREN, border=5) # Update the layout: sizer.Fit(panel) return panel def _create_buttons(self, parent): """ Creates the buttons. """ import wx sizer = wx.BoxSizer(wx.HORIZONTAL) # 'Send' button. send = wx.Button(parent, wx.ID_OK, "Send") wx.EVT_BUTTON(parent, wx.ID_OK, self._on_send) sizer.Add(send) send.SetDefault() # 'Cancel' button. cancel = wx.Button(parent, wx.ID_CANCEL, "Cancel") wx.EVT_BUTTON(parent, wx.ID_CANCEL, self._wx_on_cancel) sizer.Add(cancel, 0, wx.LEFT, 10) # 'Help' button. if len(self.help_id) > 0: help = wx.Button(parent, wx.ID_HELP, "Help") wx.EVT_BUTTON(parent, wx.ID_HELP, self._wx_on_help) sizer.Add(help, 0, wx.LEFT, 10) return sizer def _on_help(self, event): """Called when the 'Help' button is pressed. """ hp = self.service.application.get_service('apptools.help.IHelp') hp.library.show_topic(self.help_id) return ### Utility methods ####################################################### def _create_error_panel(self, parent): import wx box = wx.StaticBox(parent, -1, "Message:") sizer = wx.StaticBoxSizer(box, wx.VERTICAL) # Print the stack trace label2 = wx.StaticText(parent, -1,"The following information will be included in the report:") sizer.Add(label2, 0, wx.LEFT|wx.TOP|wx.BOTTOM|wx.CLIP_CHILDREN, border=5) details = wx.TextCtrl(parent, -1, self.msg, size=(-1,75), style=wx.TE_MULTILINE | wx.TE_READONLY | wx.HSCROLL | wx.VSCROLL | wx.TE_RICH2 | wx.CLIP_CHILDREN) details.SetSizeHints(minW=-1, minH=75) # Set the font to not be proportional font = wx.Font(12, wx.MODERN, wx.NORMAL, wx.NORMAL) details.SetStyle(0, len(self.msg), wx.TextAttr(font=font)) sizer.Add(details, 1, wx.EXPAND|wx.ALL|wx.CLIP_CHILDREN, 5) return sizer def _create_report_panel(self, parent): import wx box = wx.StaticBox(parent, -1, "Report Information:") sizer = wx.StaticBoxSizer(box, wx.VERTICAL) # Add email info ... sizer.Add(self._create_email_info(parent), 0, wx.ALL|wx.EXPAND, 5) # Add priority combo: sizer.Add(self._create_priority_combo(parent), 0, wx.ALL|wx.RIGHT, 5) # Extra comments from the user: label3 = wx.StaticText(parent, -1, "Additional Comments:") sizer.Add(label3, 0, wx.LEFT|wx.TOP|wx.BOTTOM|wx.CLIP_CHILDREN, 5) comments_field = wx.TextCtrl(parent, -1, self.comments, size=(-1,75), style=wx.TE_MULTILINE | wx.TE_RICH2 | wx.CLIP_CHILDREN) comments_field.SetSizeHints(minW=-1, minH=75) font = wx.Font(12, wx.MODERN, wx.NORMAL, wx.NORMAL) comments_field.SetStyle(0, len(self.comments), wx.TextAttr(font=font)) sizer.Add(comments_field, 1, wx.ALL|wx.EXPAND|wx.CLIP_CHILDREN, 5) wx.EVT_TEXT(parent, comments_field.GetId(), self._on_comments) # Include the project combobox? if len(self.service.mail_files) > 0: sizer.Add(self._create_project_upload(parent), 0, wx.ALL, border=5) return sizer def _create_email_info(self, parent): import wx # Layout setup .. sizer = wx.FlexGridSizer(5,2,10,10) sizer.AddGrowableCol(1) title_label = wx.StaticText(parent, -1, "Subject:") sizer.Add(title_label , 0, wx.ALL|wx.ALIGN_RIGHT) title_field = wx.TextCtrl(parent, -1, self.subject, wx.Point(-1,-1)) sizer.Add(title_field, 1, wx.EXPAND|wx.ALL|wx.ALIGN_RIGHT|wx.CLIP_CHILDREN) wx.EVT_TEXT(parent, title_field.GetId(), self._on_subject) to_label = wx.StaticText(parent, -1, "To:") sizer.Add(to_label , 0, wx.ALL|wx.ALIGN_RIGHT) to_field = wx.TextCtrl(parent, -1, self.to_address) sizer.Add(to_field, 1, wx.EXPAND|wx.ALL|wx.ALIGN_RIGHT|wx.CLIP_CHILDREN) wx.EVT_TEXT(parent, to_field.GetId(), self._on_to) cc_label = wx.StaticText(parent, -1, "Cc:") sizer.Add(cc_label, 0, wx.ALL|wx.ALIGN_RIGHT) cc_field = wx.TextCtrl(parent, -1, "") sizer.Add(cc_field, 1, wx.EXPAND|wx.ALL|wx.ALIGN_RIGHT|wx.CLIP_CHILDREN) wx.EVT_TEXT(parent, cc_field.GetId(), self._on_cc) from_label = wx.StaticText(parent, -1, "From:") sizer.Add(from_label, 0, wx.ALL|wx.ALIGN_RIGHT) from_field = wx.TextCtrl(parent, -1, self.from_address) sizer.Add(from_field, 1, wx.EXPAND|wx.ALL|wx.ALIGN_RIGHT|wx.CLIP_CHILDREN) wx.EVT_TEXT(parent, from_field.GetId(), self._on_from) smtp_label = wx.StaticText(parent, -1, "SMTP Server:") sizer.Add(smtp_label, 0, wx.ALL|wx.ALIGN_RIGHT) smtp_server_field = wx.TextCtrl(parent, -1, self.smtp_server) sizer.Add(smtp_server_field, 1, wx.EXPAND|wx.ALL|wx.ALIGN_RIGHT|wx.CLIP_CHILDREN) wx.EVT_TEXT(parent, smtp_server_field.GetId(), self._on_smtp_server) return sizer def _create_priority_combo(self, parent): import wx sizer = wx.BoxSizer(wx.HORIZONTAL) label = wx.StaticText(parent, -1, "How critical is this issue?") sizer.Add(label, 0, wx.ALL, border=0) cb = wx.ComboBox(parent, -1, self.priority, wx.Point(90, 50), wx.Size(95, -1), priority_levels, wx.CB_READONLY) sizer.Add(cb, 1, wx.EXPAND|wx.LEFT|wx.CLIP_CHILDREN, border=10) wx.EVT_COMBOBOX(parent, cb.GetId(), self._on_priority) return sizer def _create_project_upload(self, parent): import wx id = wx.NewId() cb = wx.CheckBox(parent, id, "Include Workspace Files (will increase email size) ", wx.Point(65, 80), wx.Size(-1, 20), wx.NO_BORDER) wx.EVT_CHECKBOX(parent, id, self._on_project) return cb ## UI Listeners ########################################################### def _on_subject(self, event): self.subject = event.GetEventObject().GetValue() def _on_to(self, event): self.to_address = event.GetEventObject().GetValue() def _on_cc(self, event): self.cc_address = event.GetEventObject().GetValue() def _on_from(self, event): self.from_address = event.GetEventObject().GetValue() def _on_smtp_server(self, event): self.smtp_server = event.GetEventObject().GetValue() def _on_priority(self, event): self.priority = event.GetEventObject().GetStringSelection() def _on_comments(self, event): self.comments = event.GetEventObject().GetValue() def _on_project(self, event): self.include_userdata = event.Checked() cb = event.GetEventObject() if event.Checked(): cb.SetLabel("Include Workspace Files (approx. %.2f MBytes)" % self._compute_project_size()) else: cb.SetLabel("Include Workspace Files (will increase email size)") return def _on_send(self, event): import wx # Disable the Send button while we go through the possibly # time-consuming email-sending process. button = event.GetEventObject() button.Enable(0) fromaddr, toaddrs, ccaddrs = self._create_email_addresses() message = self._create_email(fromaddr, toaddrs, ccaddrs) self.service.send_bug_report(self.smtp_server, fromaddr, toaddrs, ccaddrs, message) # save the user's preferences self.service.preferences.smtp_server = self.smtp_server self.service.preferences.to_address = self.to_address self.service.preferences.from_address = self.from_address # finally we close the dialog self._wx_on_ok(event) return ## Private ################################################################ def _create_email_addresses(self): # utility function map addresses from ui into the standard format # FIXME: We should use standard To: header parsing instead of this ad # hoc whitespace-only approach. fromaddr = self.from_address if "" == fromaddr.strip(): fromaddr = "anonymous" toaddrs = self.to_address.split() ccaddrs = self.cc_address.split() return fromaddr, toaddrs, ccaddrs def _compute_project_size(self): # determine size of email in MBytes fromaddr, toaddrs, ccaddrs = self._create_email_addresses() message = self._create_email(fromaddr, toaddrs, ccaddrs) return len(message.as_string()) / (2.0**20) def _create_email(self, fromaddr, toaddrs, ccaddrs): return self.service.create_email_message( fromaddr, toaddrs, ccaddrs, self.subject, self.priority, self.include_userdata, self.msg, self.comments, ) def _to_address_default(self): return self.service.preferences.to_address def _from_address_default(self): return self.service.preferences.from_address def _smtp_server_default(self): return self.service.preferences.smtp_server ####### EOF ############################################################# apptools-4.1.0/apptools/logger/__init__.py0000644000175100001440000000050711674464005021600 0ustar ischnellusers00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2005 by Enthought, Inc. # All rights reserved. # #----------------------------------------------------------------------------- """ Convenience functions for creating logging handlers. Part of the EnthoughtBase project. """ apptools-4.1.0/apptools/logger/log_point.py0000644000175100001440000000314211674464005022031 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Prints a stack trace every time it is called but does not halt execution of the application. Copied from Uche Ogbuji's blog """ # Standard library imports. import inspect from cStringIO import StringIO def log_point(msg='\n'): stack = inspect.stack() # get rid of logPoint's part of the stack: stack = stack[1:] stack.reverse() output = StringIO() if msg: output.write(str(msg) + '\n') for stackLine in stack: frame, filename, line, funcname, lines, unknown = stackLine if filename.endswith('/unittest.py'): # unittest.py code is a boring part of the traceback continue if filename.startswith('./'): filename = filename[2:] output.write('%s:%s in %s:\n' % (filename, line, funcname)) if lines: output.write(' %s\n' % ''.join(lines)[:-1]) s = output.getvalue() return s ## EOF ################################################################## apptools-4.1.0/apptools/logger/null_handler.py0000644000175100001440000000263011674464005022507 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A null log handler. """ # Standard library imports. import logging class NullHandler(logging.Handler): """ A null log handler. This is a quick hack so that we can start to refactor the 'logger' module since it used to add actual handlers at module load time. Now we only add this handler so that people using just the ETS library and not one of our applications won't see the warning about 'no handlers'. """ ########################################################################### # 'Handler' interface. ########################################################################### def emit(self, record): """ Emits a log record. """ pass #### EOF ###################################################################### apptools-4.1.0/apptools/sweet_pickle/0000755000175100001440000000000011674464005020664 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/sweet_pickle/tests/0000755000175100001440000000000011674464005022026 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/sweet_pickle/tests/state_function_test_case.py0000644000175100001440000001456111674464005027466 0ustar ischnellusers00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2006 by Enthought, Inc. # All rights reserved. # # Author: Dave Peterson # #----------------------------------------------------------------------------- """ Tests the state function functionality of the apptools.sweet_pickle framework. """ # Standard library imports. import unittest import logging # Enthought library imports import apptools.sweet_pickle as sweet_pickle from apptools.sweet_pickle.global_registry import _clear_global_registry from traits.api import Bool, Float, HasTraits, Int, Str logger = logging.getLogger(__name__) ############################################################################## # Classes to use within the tests ############################################################################## # Need complete package name so that mapping matches correctly. # The problem here is the Python loader that will load the same module with # multiple names in sys.modules due to relative naming. Nice. from apptools.sweet_pickle.tests.state_function_classes import Foo, Bar, Baz ############################################################################## # State functions to use within the tests ############################################################################## def bar_state_function(state): for old, new in [('b1', 'b2'), ('f1', 'f2'), ('i1', 'i2'), ('s1', 's2')]: state[new] = state[old] del state[old] state['_enthought_pickle_version'] = 2 return state ############################################################################## # class 'StateFunctionTestCase' ############################################################################## class StateFunctionTestCase(unittest.TestCase): """ Tests the state function functionality of the apptools.sweet_pickle framework. """ ########################################################################## # 'TestCase' interface ########################################################################## ### public interface ##################################################### def setUp(self): """ Creates the test fixture. Overridden here to ensure each test starts with an empty global registry. """ # Clear the global registry _clear_global_registry() # Cache a reference to the new global registry self.registry = sweet_pickle.get_global_registry() # Add the class mappings to the registry self.registry.add_mapping_to_class(Foo.__module__, Foo.__name__, Bar) self.registry.add_mapping_to_class(Bar.__module__, Bar.__name__, Baz) ########################################################################## # 'StateFunctionTestCase' interface ########################################################################## ### public interface ##################################################### def test_normal_setstate(self): """ Validates that only existing setstate methods are called when there are no registered state functions in the class chain. """ # Validate that unpickling the first class gives us an instance of # the third class with the appropriate attribute values. It will have # the default Foo values (because there is no state function to move # them) and also the default Baz values (since they inherit the # trait defaults because nothing overwrote the values.) start = Foo() end = sweet_pickle.loads(sweet_pickle.dumps(start)) self.assertEqual(True, isinstance(end, Baz)) self._assertAttributes(end, 1, (False, 1, 1, 'foo')) self._assertAttributes(end, 2, None) self._assertAttributes(end, 3, (False, 3, 3, 'baz')) # Validate that unpickling the second class gives us an instance of # the third class with the appropriate attribute values. It will have # only the Baz attributes with the Bar values (since the __setstate__ # on Baz converted the Bar attributes to Baz attributes.) start = Bar() end = sweet_pickle.loads(sweet_pickle.dumps(start)) self.assertEqual(True, isinstance(end, Baz)) self._assertAttributes(end, 2, None) self._assertAttributes(end, 3, (True, 2, 2, 'bar')) def test_unpickled_chain_functionality(self): """ Validates that the registered state functions are used when unpickling. """ # Add the state function to the registry self.registry.add_state_function_for_class(Bar, 2, bar_state_function) # Validate that unpickling the first class gives us an instance of # the third class with the appropriate attribute values. start = Foo() end = sweet_pickle.loads(sweet_pickle.dumps(start)) self.assertEqual(True, isinstance(end, Baz)) self._assertAttributes(end, 1, None) self._assertAttributes(end, 2, None) self._assertAttributes(end, 3, (False, 1, 1, 'foo')) # Validate that unpickling the second class gives us an instance of # the third class. start = Bar() end = sweet_pickle.loads(sweet_pickle.dumps(start)) self.assertEqual(True, isinstance(end, Baz)) self._assertAttributes(end, 2, None) self._assertAttributes(end, 3, (True, 2, 2, 'bar')) ### protected interface ################################################## def _assertAttributes(self, obj, suffix, values): """ Ensures that the specified object's attributes with the specified suffix have the expected values. If values is None, then the attributes shouldn't exist. """ attributeNames = ['b', 'f', 'i', 's'] for i in range(len(attributeNames)): name = attributeNames[i] + str(suffix) if values is None: self.assertEqual(False, hasattr(obj, name), 'Obj [%s] has attribute [%s]' % (obj, name)) else: self.assertEqual(values[i], getattr(obj, name), 'Obj [%s] attribute [%s] has [%s] instead of [%s]' % \ (obj, name, values[i], getattr(obj, name))) if __name__ == "__main__": unittest.main() ### EOF ###################################################################### apptools-4.1.0/apptools/sweet_pickle/tests/class_mapping_classes.py0000644000175100001440000000103011674464005026727 0ustar ischnellusers00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2006 by Enthought, Inc. # All rights reserved. # # Author: Dave Peterson # #----------------------------------------------------------------------------- ############################################################################## # Classes to use within the tests ############################################################################## class Foo: pass class Bar: pass class Baz: pass apptools-4.1.0/apptools/sweet_pickle/tests/global_registry_test_case.py0000644000175100001440000000555111674464005027630 0ustar ischnellusers00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2006 by Enthought, Inc. # All rights reserved. # # Author: Dave Peterson # #----------------------------------------------------------------------------- """ Tests the global registry functionality of the sweet_pickle framework. """ # Standard library imports. import unittest # Enthought library imports import apptools.sweet_pickle as sweet_pickle from apptools.sweet_pickle.global_registry import _clear_global_registry ############################################################################## # class 'GlobalRegistryTestCase' ############################################################################## class GlobalRegistryTestCase(unittest.TestCase): """ Tests the global registry functionality of the sweet_pickle framework. """ ########################################################################## # 'TestCase' interface ########################################################################## ### public interface ##################################################### def setUp(self): """ Creates the test fixture. Overridden here to ensure each test starts with an empty global registry. """ # Clear the global registry _clear_global_registry() # Cache a reference to the new global registry self.registry = sweet_pickle.get_global_registry() ########################################################################## # 'GlobalRegistryTestCase' interface ########################################################################## ### public interface ##################################################### def test_clearing(self): """ Validates that clearing the registry gives us a new registry. """ _clear_global_registry() self.assertNotEqual(self.registry, sweet_pickle.get_global_registry()) def test_registry_starts_empty(self): """ Validates that the registry is starting empty for each test. """ self.assertEqual(0, len(self.registry.class_map)) self.assertEqual(0, len(self.registry.state_functions)) self.assertEqual(0, len(self.registry.version_attribute_map)) self.assertEqual(0, len(self.registry._state_function_classes)) def test_returns_singleton(self): """ Validates that the getter returns the same global registry """ # Just try it a few times. self.assertEqual(self.registry, sweet_pickle.get_global_registry()) self.assertEqual(self.registry, sweet_pickle.get_global_registry()) self.assertEqual(self.registry, sweet_pickle.get_global_registry()) if __name__ == "__main__": unittest.main() ### EOF ###################################################################### apptools-4.1.0/apptools/sweet_pickle/tests/class_mapping_test_case.py0000644000175100001440000000770011674464005027256 0ustar ischnellusers00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2006 by Enthought, Inc. # All rights reserved. # # Author: Dave Peterson # #----------------------------------------------------------------------------- """ Tests the class mapping functionality of the enthought.pickle framework. """ # Standard library imports. import unittest # Enthought library imports import apptools.sweet_pickle as sweet_pickle from apptools.sweet_pickle.global_registry import _clear_global_registry ############################################################################## # Classes to use within the tests ############################################################################## # Need complete package name so that mapping matches correctly. # The problem here is the Python loader that will load the same module with # multiple names in sys.modules due to relative naming. Nice. from apptools.sweet_pickle.tests.class_mapping_classes import Foo, Bar, Baz ############################################################################## # class 'ClassMappingTestCase' ############################################################################## class ClassMappingTestCase(unittest.TestCase): """ Tests the class mapping functionality of the apptools.sweet_pickle framework. """ ########################################################################## # 'TestCase' interface ########################################################################## ### public interface ##################################################### def setUp(self): """ Creates the test fixture. Overridden here to ensure each test starts with an empty global registry. """ # Clear the global registry _clear_global_registry() # Cache a reference to the new global registry self.registry = sweet_pickle.get_global_registry() ########################################################################## # 'ClassMappingTestCase' interface ########################################################################## ### public interface ##################################################### def test_infinite_loop_detection(self): """ Validates that the class mapping framework detects infinite loops of class mappings. """ # Add mappings to the registry self.registry.add_mapping_to_class(Foo.__module__, Foo.__name__, Bar) self.registry.add_mapping_to_class(Bar.__module__, Bar.__name__, Baz) self.registry.add_mapping_to_class(Baz.__module__, Baz.__name__, Foo) # Validate that an exception is raised when trying to unpickle an # instance anywhere within the circular definition. def fn(o): sweet_pickle.loads(sweet_pickle.dumps(o)) self.assertRaises(sweet_pickle.UnpicklingError, fn, Foo()) self.assertRaises(sweet_pickle.UnpicklingError, fn, Bar()) self.assertRaises(sweet_pickle.UnpicklingError, fn, Baz()) def test_unpickled_class_mapping(self): # Add the mappings to the registry self.registry.add_mapping_to_class(Foo.__module__, Foo.__name__, Bar) self.registry.add_mapping_to_class(Bar.__module__, Bar.__name__, Baz) # Validate that unpickling the first class gives us an instance of # the third class. start = Foo() end = sweet_pickle.loads(sweet_pickle.dumps(start)) self.assertEqual(True, isinstance(end, Baz)) # Validate that unpickling the second class gives us an instance of # the third class. start = Bar() end = sweet_pickle.loads(sweet_pickle.dumps(start)) self.assertEqual(True, isinstance(end, Baz)) if __name__ == "__main__": unittest.main() ### EOF ###################################################################### apptools-4.1.0/apptools/sweet_pickle/tests/two_stage_unpickler_test_case.py0000644000175100001440000001021511674464005030501 0ustar ischnellusers00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2008 by Enthought, Inc. # All rights reserved. # # Author: Dave Peterson # Prabhu Ramachandran #----------------------------------------------------------------------------- # Test cases. import random import pickle import apptools.sweet_pickle as sweet_pickle ######################################## # Usecase1: generic case class A(object): def __init__(self, b=None): self.x = 0 self.set_b(b) def set_b(self, b): self.b_ref = b if b and hasattr(b, 'y'): self.x = b.y def __setstate__(self, state): self.__dict__.update(state) self.set_b(self.b_ref) def __initialize__(self): while self.b_ref is None and self.b_ref.y != 0: yield True self.set_b(self.b_ref) class B(object): def __init__(self, a=None): self.y = 0 self.set_a(a) def __getstate__(self): state = self.__dict__.copy() del state['y'] return state def __setstate__(self, state): self.__dict__.update(state) self.set_a(self.a_ref) def set_a(self, a): self.a_ref = a if a and hasattr(a, 'x'): self.y = a.x def __initialize__(self): while self.a_ref is None and self.a_ref.x != 0: yield True self.set_a(self.a_ref) def test_generic(): print '\nRunning generic test...' a = A() b = B() a.x = random.randint(1, 100) b.set_a(a) a.set_b(b) value = a.x # This will fail, even though we have a __setstate__ method. s = pickle.dumps(a) new_a = pickle.loads(s) try: print '\ta.x: %s' % new_a.x print '\ta.b_ref.y: %s' % new_a.b_ref.y except Exception, msg: print '\t%s' % 'Expected Error'.center(75,'*') print '\t%s' % msg print '\t%s' % ('*'*75) # This will work! s = pickle.dumps(a) new_a = sweet_pickle.loads(s) assert new_a.x == new_a.b_ref.y == value print 'Generic test succesfull.\n\n' ######################################## # Usecase2: Toy Application import re class StringFinder(object): def __init__(self, source, pattern): self.pattern = pattern self.source = source self.data = [] def __getstate__(self): s = self.__dict__.copy() del s['data'] return s def __initialize__(self): while not self.source.initialized: yield True self.find() def find(self): pattern = self.pattern string = self.source.data self.data = [(x.start(), x.end()) \ for x in re.finditer(pattern, string)] class XMLFileReader(object): def __init__(self, file_name): self.data = '' self.initialized = False self.file_name = file_name self.read() def __getstate__(self): s = self.__dict__.copy() del s['data'] del s['initialized'] return s def __setstate__(self, state): self.__dict__.update(state) self.read() def read(self): # Make up random data from the filename data = [10*x for x in self.file_name] random.shuffle(data) self.data = ' '.join(data) self.initialized = True class Application(object): def __init__(self): self.reader = XMLFileReader('some_test_file.xml') self.finder = StringFinder(self.reader, 'e') def get(self): print '\t%s' % self.finder.data print '\t%s' % self.reader.data def test_toy_app(): print '\nRunning toy app test...' a = Application() a.finder.find() a.get() s = pickle.dumps(a) b = pickle.loads(s) # Won't work. try: b.get() except Exception, msg: print '\t%s' % 'Expected Error'.center(75,'*') print '\t%s' % msg print '\t%s' % ('*'*75) # Works fine. c = sweet_pickle.loads(s) c.get() print 'Toy app test succesfull.\n\n' if __name__ == '__main__': test_generic() test_toy_app() print 'ALL TESTS SUCCESFULL\n' apptools-4.1.0/apptools/sweet_pickle/tests/updater_test_case.py0000644000175100001440000003160711674464005026105 0ustar ischnellusers00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2006 by Enthought, Inc. # All rights reserved. # # Author: Dave Peterson # #----------------------------------------------------------------------------- """ Tests the updater functionality of the sweet_pickle framework. """ # Standard library imports. import unittest import logging # Enthought library imports import apptools.sweet_pickle as sweet_pickle from apptools.sweet_pickle.global_registry import _clear_global_registry logger = logging.getLogger(__name__) ############################################################################## # class 'UpdaterTestCase' ############################################################################## class UpdaterTestCase(unittest.TestCase): """ Tests the updater functionality of the sweet_pickle framework. """ ########################################################################## # 'TestCase' interface ########################################################################## ### public interface ##################################################### def setUp(self): """ Creates the test fixture. Overridden here to ensure each test starts with an empty global registry. """ # Clear the global registry _clear_global_registry() # Cache a reference to the new global registry self.registry = sweet_pickle.get_global_registry() ########################################################################## # 'UpdaterTestCase' interface ########################################################################## ### public interface ##################################################### def test_add_mapping(self): """ Validates the behavior of the add_mapping function. """ # Add a single mapping and validate that all it did was add a class # mapping and that the mapping has the expected values. key = ('foo', 'Foo') value = ('bar', 'Bar') self.registry.add_mapping(key[0], key[1], value[0], value[1]) self._validate_exactly_one_class_map(key, value) # Overwrite with a new mapping and validate the state is what we # expect. value = ('baz', 'Baz') self.registry.add_mapping(key[0], key[1], value[0], value[1]) self._validate_exactly_one_class_map(key, value) def test_add_mapping_to_class(self): """ Validates the behavior of the add_mapping_to_class function. """ # Add a single mapping and validate that all it did was add a class # mapping and that the mapping has the expected values. key = ('foo', 'Foo') class Bar: pass value = (Bar.__module__, 'Bar') self.registry.add_mapping_to_class(key[0], key[1], Bar) self._validate_exactly_one_class_map(key, value) # Overwrite with a new mapping and validate the state is what we # expect. class Baz: pass value = (Baz.__module__, 'Baz') self.registry.add_mapping_to_class(key[0], key[1], Baz) self._validate_exactly_one_class_map(key, value) def test_add_mappings(self): """ Validates the behavior of the add_mappings function. """ # Add a single mapping and validate that all it did was add a class # mapping and that the mapping has the expected values key = ('foo', 'Foo') value = ('bar', key[1]) names = [key[1]] self.registry.add_mappings(key[0], value[0], names) self._validate_exactly_one_class_map(key, value) # Add multiple mappings and validate that the registry has the expected # values. key = ('foo', 'Foo') value = ('bar', 'Bar') names = ['Foo', 'Bar', 'Baz', 'Enthought'] self.registry.add_mappings(key[0], value[0], names) self.assertEqual(len(names), len(self.registry.class_map)) self.assertEqual(0, len(self.registry.state_functions)) self.assertEqual(0, len(self.registry.version_attribute_map)) self.assertEqual(0, len(self.registry._state_function_classes)) items = [] for n in names: items.append( ((key[0], n), (value[0], n)) ) self._validate_class_map_contents(items) def test_add_state_function(self): """ Validates the behavior of the add_state_function function. """ # Add a single function and validate that all it did was add a state # mapping and that the mapping has the expected values. key = ('foo', 'Foo', 1) def fn(): pass self.registry.add_state_function(key[0], key[1], key[2], fn) self._validate_exactly_one_state_function(key, [fn]) # Add an additional function for the same state and validate the state # is what we expect. def fn2(): pass self.registry.add_state_function(key[0], key[1], key[2], fn2) self._validate_exactly_one_state_function(key, [fn, fn2]) # Add a state function for another version of the same class and # validate that all the values are as expected. key2 = ('foo', 'Foo', 2) self.registry.add_state_function(key2[0], key2[1], key2[2], fn2) self.assertEqual(0, len(self.registry.class_map)) self.assertEqual(2, len(self.registry.state_functions)) self.assertEqual(0, len(self.registry.version_attribute_map)) self.assertEqual(1, len(self.registry._state_function_classes)) self._validate_state_function_contents( [(key, [fn, fn2]), (key2, [fn2])], {(key[0], key[1]): 3} ) def test_add_state_function_for_class(self): """ Validates the behavior of the add_state_function_for_class function. """ # Add a single function and validate that all it did was add a state # mapping and that the mapping has the expected values. class Bar: pass key = (Bar.__module__, 'Bar', 1) def fn(): pass self.registry.add_state_function_for_class(Bar, key[2], fn) self._validate_exactly_one_state_function(key, [fn]) # Add an additional function for the same state and validate the state # is what we expect. def fn2(): pass self.registry.add_state_function_for_class(Bar, key[2], fn2) self._validate_exactly_one_state_function(key, [fn, fn2]) # Add a state function for another class and validate that all the # values are as expected. class Baz: pass key2 = (Baz.__module__, 'Baz', 2) self.registry.add_state_function_for_class(Baz, key2[2], fn2) self.assertEqual(0, len(self.registry.class_map)) self.assertEqual(2, len(self.registry.state_functions)) self.assertEqual(0, len(self.registry.version_attribute_map)) self.assertEqual(2, len(self.registry._state_function_classes)) self._validate_state_function_contents( [(key, [fn, fn2]), (key2, [fn2])], {(key[0], key[1]): 2, (key2[0], key2[1]): 1} ) def test_merge_updater(self): """ Validates the behavior of the merge_updater function. """ # Merge in one update and validate the state of the registry is as # expected. def fn1(): pass updater = sweet_pickle.Updater( class_map = { ('foo', 'Foo'): ('foo.bar', 'Foo'), }, state_functions = { ('foo', 'Foo', 1): [fn1], }, version_attribute_map = { ('foo', 'Foo'): 'version', }, ) self.registry.merge_updater(updater) self.assertEqual(1, len(self.registry.class_map)) self.assertEqual(1, len(self.registry.state_functions)) self.assertEqual(1, len(self.registry.version_attribute_map)) self.assertEqual(1, len(self.registry._state_function_classes)) self._validate_class_map_contents(updater.class_map.items()) counts = {('foo', 'Foo'): 1} self._validate_state_function_contents(updater.state_functions.items(), counts) # Merge in a second updater and validate the state of the registry is # as expected. def fn2(): pass updater2 = sweet_pickle.Updater( class_map = { ('foo.bar', 'Foo'): ('bar', 'Bar'), ('bar', 'Bar'): ('foo.bar.baz', 'Baz'), }, state_functions = { ('foo', 'Foo', 1): [fn2], ('foo', 'Foo', 2): [fn2], }, version_attribute_map = { ('foo.bar', 'Foo'): '_version', }, ) self.registry.merge_updater(updater2) self.assertEqual(3, len(self.registry.class_map)) self.assertEqual(2, len(self.registry.state_functions)) self.assertEqual(2, len(self.registry.version_attribute_map)) self.assertEqual(1, len(self.registry._state_function_classes)) self._validate_class_map_contents(updater.class_map.items() + \ updater2.class_map.items()) counts = {('foo', 'Foo'): 3} self._validate_state_function_contents( [ (('foo', 'Foo', 1), [fn1, fn2]), (('foo', 'Foo', 2), [fn2]) ], counts) def test_registry_starts_empty(self): """ Validates that the registry is starting empty for each test. """ self.assertEqual(0, len(self.registry.class_map)) self.assertEqual(0, len(self.registry.state_functions)) self.assertEqual(0, len(self.registry.version_attribute_map)) self.assertEqual(0, len(self.registry._state_function_classes)) ### protected interface ################################################## def _validate_class_map_contents(self, items): """ Validates that the registry's class_map contains the specified items. """ for key, value in items: self.assertEqual(True, key in self.registry.class_map, 'Key ' + str(key) + ' not in class_map') self.assertEqual(value, self.registry.class_map[key], str(value) + ' != ' + str(self.registry.class_map[key]) + \ ' for key ' + str(key)) self.assertEqual(True, self.registry.has_class_mapping(key[0], key[1]), 'Registry reports no class mapping for key ' + str(key)) def _validate_exactly_one_class_map(self, key, value): """ Validates that the registry has exactly one class_map entry with the specified key and value. """ self.assertEqual(1, len(self.registry.class_map)) self.assertEqual(0, len(self.registry.state_functions)) self.assertEqual(0, len(self.registry.version_attribute_map)) self.assertEqual(0, len(self.registry._state_function_classes)) self._validate_class_map_contents([(key, value)]) def _validate_exactly_one_state_function(self, key, value): """ Validates that the registry has exactly one state_function entry with the specified key and value. """ self.assertEqual(0, len(self.registry.class_map)) self.assertEqual(1, len(self.registry.state_functions)) self.assertEqual(0, len(self.registry.version_attribute_map)) self.assertEqual(1, len(self.registry._state_function_classes)) self.assertEqual(key, self.registry.state_functions.keys()[0]) self.assertEqual(value, self.registry.state_functions[key]) classes_key = (key[0], key[1]) self.assertEqual(classes_key, self.registry._state_function_classes.keys()[0]) self.assertEqual(len(value), self.registry._state_function_classes[classes_key]) def _validate_state_function_contents(self, items, counts): """ Validates that the registry's state functions contains the specified items and the class count matches the specified count. """ for key, value in items: self.assertEqual(True, key in self.registry.state_functions, 'Key ' + str(key) + ' not in state functions') self.assertEqual(value, self.registry.state_functions[key], str(value) + ' != ' + \ str(self.registry.state_functions[key]) + ' for key ' + \ str(key)) self.assertEqual(True, self.registry.has_state_function( key[0], key[1]), 'Registry reports no state function for key ' + str(key)) classes_key = (key[0], key[1]) count = counts[classes_key] self.assertEqual(count, self.registry._state_function_classes[classes_key]) if __name__ == "__main__": unittest.main() ### EOF ###################################################################### apptools-4.1.0/apptools/sweet_pickle/tests/state_function_classes.py0000644000175100001440000000325111674464005027143 0ustar ischnellusers00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2006 by Enthought, Inc. # All rights reserved. # # Author: Dave Peterson # #----------------------------------------------------------------------------- # Standard library imports import logging # Enthought library imports import apptools.sweet_pickle as sweet_pickle from apptools.sweet_pickle.global_registry import _clear_global_registry from traits.api import Bool, Float, HasTraits, Int, Str logger = logging.getLogger(__name__) ############################################################################## # Classes to use within the tests ############################################################################## class Foo(HasTraits): _enthought_pickle_version = Int(1) b1 = Bool(False) f1 = Float(1) i1 = Int(1) s1 = Str('foo') class Bar(HasTraits): _enthought_pickle_version = Int(2) b2 = Bool(True) f2 = Float(2) i2 = Int(2) s2 = Str('bar') class Baz(HasTraits): _enthought_pickle_version = Int(3) b3 = Bool(False) f3 = Float(3) i3 = Int(3) s3 = Str('baz') def __setstate__(self, state): logger.debug('Running Baz\'s original __setstate__') if state['_enthought_pickle_version'] < 3: info = [('b2', 'b3'), ('f2', 'f3'), ('i2', 'i3'), ('s2', 's3')] for old, new in info: if old in state: state[new] = state[old] del state[old] state['_enthought_pickle_version'] = 3 self.__dict__.update(state) ### EOF ###################################################################### apptools-4.1.0/apptools/sweet_pickle/tests/__init__.py0000644000175100001440000000000111674464005024126 0ustar ischnellusers00000000000000 apptools-4.1.0/apptools/sweet_pickle/versioned_unpickler.py0000644000175100001440000005114111674464005025312 0ustar ischnellusers00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2005-2008 by Enthought, Inc. # All rights reserved. # # Author: Dave Peterson # Author: Duncan Child # #----------------------------------------------------------------------------- # The code for two-stage unpickling support has been taken from a PEP draft # prepared by Dave Peterson and Prabhu Ramachandran. """ An unpickler that is tolerant of class refactorings, and implements a two-stage pickling process to make it possible to unpickle complicated Python object hierarchies where the unserialized state of an object depends on the state of other objects in the same pickle. """ # Standard library imports. import logging import new from os import path from pickle import Unpickler, UnpicklingError, BUILD import sys from types import DictionaryType, GeneratorType # Enthought library imports from traits.api import HasTraits, Instance # Setup a logger for this module logger = logging.getLogger(__name__) ############################################################################## # constants ############################################################################## # The name we backup the original setstate method to. _BACKUP_NAME = '__enthought_sweet_pickle_original_setstate__' # The name of the setstate method we hook _SETSTATE_NAME = '__setstate__' # The name we store our unpickling data under. _UNPICKLER_DATA = '__enthought_sweet_pickle_unpickler__' ############################################################################## # function '__replacement_setstate__' ############################################################################## def __replacement_setstate__(self, state): """ Called to enable an unpickler to modify the state of this instance. """ # Retrieve the unpickling information and use it to let the unpickler # modify our state. unpickler, module, name = getattr(self, _UNPICKLER_DATA) state = unpickler.modify_state(self, state, module, name) # If we were given a state, apply it to this instance now. if state is not None: # Save our state logger.debug('Final state: %s', state) self.__dict__.update(state) ############################################################################## # function 'load_build_with_meta_data' ############################################################################## def load_build_with_meta_data(self): """ Called prior to the actual load_build() unpickling method which primes the state dictionary with meta-data. """ # Access the state object and check if it is a dictionary (state may also be # a tuple, which is used for other unpickling build operations). Proceed to # the standard load_build() if the state obj is not a dict. state = self.stack[-1] if type(state) == DictionaryType: # If a file object is used, reference the file name if hasattr(self._file, 'name'): pickle_file_name = path.abspath(self._file.name) else : pickle_file_name = "" # Add any meta-data needed by __setstate__() methods here... state['_pickle_file_name'] = pickle_file_name # Call the standard load_build() method return self.load_build() ############################################################################## # class 'NewUnpickler' ############################################################################## class NewUnpickler(Unpickler): """ An unpickler that implements a two-stage pickling process to make it possible to unpickle complicated Python object hierarchies where the unserialized state of an object depends on the state of other objects in the same pickle. """ def load(self, max_pass=-1): """Read a pickled object representation from the open file. Return the reconstituted object hierarchy specified in the file. """ # List of objects to be unpickled. self.objects = [] # We overload the load_build method. dispatch = self.dispatch dispatch[BUILD] = NewUnpickler.load_build # call the super class' method. ret = Unpickler.load(self) self.initialize(max_pass) self.objects = [] # Reset the Unpickler's dispatch table. dispatch[BUILD] = Unpickler.load_build return ret def initialize(self, max_pass): # List of (object, generator) tuples that initialize objects. generators = [] # Execute object's initialize to setup the generators. for obj in self.objects: if hasattr(obj, '__initialize__') and \ callable(obj.__initialize__): ret = obj.__initialize__() if isinstance(ret, GeneratorType): generators.append((obj, ret)) elif ret is not None: raise UnpicklingError('Unexpected return value from ' '__initialize__. %s returned %s' % (obj, ret)) # Ensure a maximum number of passes if max_pass < 0: max_pass = len(generators) # Now run the generators. count = 0 while len(generators) > 0: count += 1 if count > max_pass: not_done = [x[0] for x in generators] msg = """Reached maximum pass count %s. You may have a deadlock! The following objects are uninitialized: %s""" % (max_pass, not_done) raise UnpicklingError(msg) for o, g in generators[:]: try: g.next() except StopIteration: generators.remove((o, g)) # Make this a class method since dispatch is a class variable. # Otherwise, supposing the initial sweet_pickle.load call (which would # have overloaded the load_build method) makes a pickle.load call at some # point, we would have the dispatch still pointing to # NewPickler.load_build whereas the object being passed in will be an # Unpickler instance, causing a TypeError. def load_build(cls, obj): # Just save the instance in the list of objects. if isinstance(obj, NewUnpickler): obj.objects.append(obj.stack[-2]) Unpickler.load_build(obj) load_build = classmethod(load_build) ############################################################################## # class 'VersionedUnpickler' ############################################################################## class VersionedUnpickler(NewUnpickler, HasTraits): """ An unpickler that is tolerant of class refactorings. This class reads in a pickled file and applies the transforms specified in its updater to generate a new hierarchy of objects which are at the current version of the classes they are instances of. Note that the creation of an updater is kept out of this class to ensure that the class can be reused in different situations. However, if no updater is provided during construction, then the global registry updater will be used. """ ########################################################################## # Traits ########################################################################## ### public 'VersionedUnpickler' interface ################################ # The updater used to modify the objects being unpickled. updater = Instance('apptools.sweet_pickle.updater.Updater') ########################################################################## # 'object' interface ########################################################################## ### operator methods ##################################################### def __init__(self, file, **kws): super(VersionedUnpickler, self).__init__(file) self._file = file if self.updater is None: from global_registry import get_global_registry self.updater = get_global_registry() logger.debug('VersionedUnpickler [%s] using Updater [%s]', self, self.updater) # Update the BUILD instruction to use an overridden load_build method # NOTE: this is being disabled since, on some platforms, the object # is replaced with a regular Unpickler instance, creating a traceback: # AttributeError: Unpickler instance has no attribute '_file' # ...not sure how this happens since only a VersionedUnpickler has # the BUILD instruction replaced with one that uses _file, and it # should have _file defined. #self.dispatch[BUILD] = load_build_with_meta_data ########################################################################## # 'Unpickler' interface ########################################################################## ### public interface ##################################################### def find_class(self, module, name): """ Returns the class definition for the named class within the specified module. Overridden here to: - Allow updaters to redirect to a different class, possibly within a different module. - Ensure that any setstate hooks for the class are called when the instance of this class is unpickled. """ # Remove any extraneous characters that an Unpickler might handle # but a user wouldn't have included in their mapping definitions. module = module.strip() name = name.strip() # Attempt to find the class, this may cause a new mapping for that # very class to be introduced. That's why we ignore the result. try: klass = super(VersionedUnpickler, self).find_class(module, name) except: pass # Determine the target class that the requested class should be # mapped to according to our updater. The target class is the one # at the end of any chain of mappings. original_module, original_name = module, name if self.updater is not None and \ self.updater.has_class_mapping(module, name): module, name = self._get_target_class(module, name) if module != original_module or name != original_name: logger.debug('Unpickling [%s.%s] as [%s.%s]', original_module, original_name, module, name) # Retrieve the target class definition try: klass = super(VersionedUnpickler, self).find_class(module, name) except Exception, e: from apptools.sweet_pickle import UnpicklingError logger.debug('Traceback when finding class [%s.%s]:' \ % (module, name), exc_info=True) raise UnpicklingError('Unable to load class [%s.%s]. ' 'Original exception was, "%s". map:%s' % ( module, name, str(e), self.updater.class_map)) # Make sure we run the updater's state functions if any are declared # for the target class. if self.updater is not None \ and self._has_state_function(original_module, original_name): self._add_unpickler(klass, original_module, original_name) return klass ########################################################################## # 'VersionedUnpickler' interface ########################################################################## ### public interface ##################################################### def modify_state(self, obj, state, module, name): """ Called to update the specified state dictionary, which represents the class of the specified name within the specified module, to complete the unpickling of the specified object. """ # Remove our setstate hook and associated data to ensure that # instances unpickled through some other framework don't call us. # IMPORTANT: Do this first to minimize the time this hook is in place! self._remove_unpickler(obj.__class__) # Determine what class and version we're starting from and going to. # If there is no version information, then assume version 0. (0 is # like an unversioned version.) source_key = self.updater.get_version_attribute(module, name) source_version = state.get(source_key, 0) target_key = self.updater.get_version_attribute( obj.__class__.__module__, obj.__class__.__name__) target_version = getattr(obj, target_key, 0) # Iterate through all the updates to the state by going one version # at a time. Note that we assume there is exactly one path from our # starting class and version to our ending class and version. As a # result, we assume we update a given class to its latest version # before looking for any class mappings. Note that the version in the # updater is the version to convert *TO*. version = source_version next_version = version + 1 while True: # Iterate through all version updates for the current class. key = self.updater.get_version_attribute(module, name) while (module, name, next_version) in self.updater.state_functions: functions = self.updater.state_functions[(module, name, next_version)] for f in functions: logger.debug('Modifying state from [%s.%s (v.%s)] to ' + \ '[%s.%s (v.%s)] using function %s', module, name, version, module, name, next_version, f) state = f(state) # Avoid infinite loops due to versions not changing. new_version = state.get(key, version) if new_version == version: new_version = version + 1 version = new_version next_version = version + 1 # If there is one, move to the next class in the chain. (We # explicitly keep the version number the same.) if self.updater.has_class_mapping(module, name): original_module, original_name = module, name module, name = self.updater.class_map[(module, name)] logger.debug('Modifying state from [%s.%s (v.%s)] to ' + \ '[%s.%s (v.%s)]', original_module, original_name, version, module, name, version) else: break # If one exists, call the final class's setstate method. According to # standard pickling protocol, this method will apply the state to the # instance so our state becomes None so that we don't try to apply our # unfinished state to the object. fn = getattr(obj, _SETSTATE_NAME, None) if fn is not None: fn(state) result = None version = getattr(obj, target_key) else: result = state # Something is wrong if we aren't at our target class and version! if module != obj.__class__.__module__ \ or name != obj.__class__.__name__ \ or version != target_version: from apptools.sweet_pickle import UnpicklingError raise UnpicklingError('Unexpected state! Got ' + \ '[%s.%s (v.%s)] expected [%s.%s (v.%s)]' % (module, name, version, obj.__class__.__module__, obj.__class__.__name__, target_version)) return result ### protected interface ################################################## def _add_unpickler(self, klass, module, name): """ Modifies the specified class so that our 'modify_state' method is called when its next instance is unpickled. """ logger.debug('Adding unpickler hook to [%s]', klass) # Replace the existing setstate method with ours. self._backup_setstate(klass) m = new.instancemethod(__replacement_setstate__, None, klass) setattr(klass, _SETSTATE_NAME, m) # Add the information necessary to allow this unpickler to run setattr(klass, _UNPICKLER_DATA, (self, module, name)) def _backup_setstate(self, klass): """ Backs up the specified class's setstate method. """ # We only need to back it up if it actually exists. method = getattr(klass, _SETSTATE_NAME, None) if method is not None: logger.debug('Backing up method [%s] to [%s] on [%s]', _SETSTATE_NAME, _BACKUP_NAME, klass) m = new.instancemethod(method, None, klass) setattr(klass, _BACKUP_NAME, m) def _get_target_class(self, module, name): """ Returns the class info that the class, within the specified module and with the specified name, should be instantiated as according to our associated updater. This is done in a manner that allows for chaining of class mappings but is tolerant of the fact that a mapping away from an intermediate class may not be registered until an attempt is made to load that class. """ # Keep a record of the original class asked for. original_module, original_name = module, name # Iterate through any mappings in a manner that allows us to detect any # infinite loops. visited = [] while self.updater.has_class_mapping(module, name): if (module, name) in visited: from apptools.sweet_pickle import UnpicklingError raise UnpicklingError('Detected infinite loop in class ' + \ 'mapping from [%s.%s] to [%s.%s] within Updater [%s]' % \ (original_module, original_name, module, name, self.updater)) visited.append( (module, name) ) # Get the mapping for the current class and try loading the class # to ensure any mappings away from it are registered. module, name = self.updater.class_map[(module, name)] try: super(VersionedUnpickler, self).find_class(module, name) except: logger.exception("_get_target_class can't find: %s" % (module, name)) pass return module, name def _has_state_function(self, module, name): """ Returns True if the updater contains any state functions that could be called by unpickling an instance of the class identified by the specified module and name. Note: If we had a version number we could tell for sure, but we don't have one so we'll have to settle for 'could' be called. """ result = False # Iterate through all the class mappings the requested class would # go through. If any of them have a state function, then we've # determined our answer and can stop searching. # # Note we don't need to check for infinite loops because we're only # ever called after '_get_target_class' which detects the infinite # loops. while not result: result = self.updater.has_state_function(module, name) if not result: if self.updater.has_class_mapping(module, name): module, name = self.updater.class_map[(module, name)] else: break return result def _remove_unpickler(self, klass): """ Restores the specified class to its unmodified state. Meaning we won't get called when its next instance is unpickled. """ logger.debug('Removing unpickler hook from [%s]', klass) # Restore the backed up setstate method self._restore_setstate(klass) # Remove the unpickling data attached to the class. This ensures we # don't pollute the 'real' attributes of the class. delattr(klass, _UNPICKLER_DATA) def _restore_setstate(self, klass): """ Restores the original setstate method back to its rightful place. """ # We only need to restore if the backup actually exists. method = getattr(klass, _BACKUP_NAME, None) if method is not None: logger.debug('Restoring method [%s] to [%s] on [%s]', _BACKUP_NAME, _SETSTATE_NAME, klass) delattr(klass, _BACKUP_NAME) m = new.instancemethod(method, None, klass) setattr(klass, _SETSTATE_NAME, m) # Otherwise, we simply remove our setstate. else: delattr(klass, _SETSTATE_NAME) ### EOF ###################################################################### apptools-4.1.0/apptools/sweet_pickle/updater.py0000644000175100001440000003052011674464005022702 0ustar ischnellusers00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2005, 2006 by Enthought, Inc. # All rights reserved. # # Author: Dave Peterson # Author: Duncan Child # #----------------------------------------------------------------------------- """ A record of refactorings to be performed during unpickling of objects. """ # Standard library imports import logging # Enthought library imports from traits.api import Dict, HasPrivateTraits, Int, List, Tuple, Str logger = logging.getLogger(__name__) ############################################################################## # class 'Updater' ############################################################################## class Updater(HasPrivateTraits): """ A record of refactorings to be performed during unpickling of objects. """ ########################################################################## # Traits ########################################################################## ### public 'Updater' interface ########################################### # Mappings from a pickled class to a class it should be unpickled as. # # The keys are a tuple of the source class's module and class names in # that order. The values are the target class's module and class names # in that order. class_map = Dict(Tuple(Str, Str), Tuple(Str, Str)) # State functions that should be called to convert state from one version # of a class to another. # # The keys are a tuple of the class's module name, class name, and version # in that order. The values are a list of functions to be called during # unpickling to do the state conversion. Note that the version in the # key represents the version that the function converts *TO*. state_functions = Dict(Tuple(Str, Str, Int), List) # Our record of the attribute that records the version number for a # specific class. If no record is found for a given class, then the # default value is used instead -- see '_default_version_attribute'. # # The key is a tuple of the class's module name and class name in that # order. The value is the name of the version attribute. version_attribute_map = Dict(Tuple(Str, Str), Str) ### protected 'Updater' interface ######################################## # The default name of the attribute that declares the version of a class # or class instance. _default_version_attribute = '_enthought_pickle_version' # A record of which classes we have state functions for. # # The keys are a tuple of the class's module name and class name in that # order. The values are reference counts. _state_function_classes = Dict(Tuple(Str, Str), Int) ########################################################################## # 'Updater' interface ########################################################################## ### public interface ##################################################### def add_mapping(self, source_module, source_name, target_module, target_name): """ Adds a mapping from the class with the source name in the source module to the class with the target name in the target module. """ self.class_map[(source_module, source_name)] = (target_module, target_name) def add_mapping_to_class(self, source_module, source_name, target_class): """ Convenience method to add a mapping, from the class with the source name in the source module to the target class. """ self.add_mapping(source_module, source_name, target_class.__module__, target_class.__name__) def add_mappings(self, source_module, target_module, class_names): """ Adds mappings, from the specified source module to the specified target module, for each of the class names in the specified list. """ for name in class_names: self.add_mapping(source_module, name, target_module, name) def add_state_function(self, module, name, target_version, function): """ Adds the specified function as a state function to be called to convert an instance of the class with the specified name within the specified module *TO* the specified version. Note that the framework handles calling of state functions to make the smallest version jumps possible. """ key = (module, name, target_version) list = self.state_functions.setdefault(key, []) list = list[:] # Copy necessary because traits only recognizes list # changes by list instance - not its contents. list.append(function) self.state_functions[key] = list def add_state_function_for_class(self, klass, target_version, function): """ Convenience method to add the specified function as a state function to be called to convert an instance of the specified class *TO* the specified version. """ self.add_state_function(klass.__module__, klass.__name__, target_version, function) def declare_version_attribute(self, module, name, attribute_name): """ Adds the specified attribute name as the version attribute for the class within the specified module with the specified name. """ self.version_attribute_map[(module, name)] = attribute_name def declare_version_attribute_for_class(self, klass, attribute_name): """ Covenience method to add the specified attribute name as the version attribute for the specified class. """ self.declare_version_attribute(klass.__module__, klass.__name__, attribute_name) def get_version_attribute(self, module, name): """ Returns the name of the version attribute for the class of the specified name within the specified module. """ return self.version_attribute_map.get( (module, name), self._default_version_attribute) def has_class_mapping(self, module, name): """ Returns True if this updater contains a class mapping for the class identified by the specified module and class name. """ return (module, name) in self.class_map def has_state_function(self, module, name): """ Returns True if this updater contains any state functions for the class identified by the specified module and class name. """ return (module, name) in self._state_function_classes def merge_updater(self, updater): """ Merges the mappings and state functions from the specified updater into this updater. """ self.class_map.update(updater.class_map) self.version_attribute_map.update(updater.version_attribute_map) # The state functions dictionary requires special processing because # each value is a list and we don't just want to replace the existing # list with only the new content. for key, value in updater.state_functions.items(): if isinstance(value, list) and len(value) > 0: funcs = self.state_functions.setdefault(key, []) funcs = funcs[:] # Copy necessary because traits only recognizes # funcs changes by funcs instance - not its # contents. funcs.extend(value) self.state_functions[key] = funcs ### trait handlers ####################################################### def _class_map_changed(self, old, new): logger.debug('Detected class_map change from [%s] to [%s] in [%s]', old, new, self) def _class_map_items_changed(self, event): for o in event.removed: logger.debug('Detected [%s] removed from class_map in [%s]', o, self) for k, v in event.changed.items(): logger.debug('Detected [%s] changed from [%s] to [%s] in ' + \ 'class_map in [%s]', k, v, self.class_map[k], self) for k, v in event.added.items(): logger.debug('Detected mapping from [%s] to [%s] added to ' + \ 'class_map in [%s]', k, v, self) def _state_functions_changed(self, old, new): logger.debug('Detected state_functions changed from [%s] to [%s] ' + \ 'in [%s]', old, new, self) # Update our record of which classes we have state functions for. # All of our old state functions are gone so we simply need to rescan # the new functions. self._state_function_classes.clear() for key, value in new.items(): module, name, version = key klass_key = (module, name) count = self._state_function_classes.setdefault(klass_key, 0) self._state_function_classes[klass_key] = count + len(value) def _state_functions_items_changed(self, event): # Decrement our reference counts for the classes we no longer # have state functions for. If the reference count reaches zero, # remove the record completely. for k, v in event.removed.items(): logger.debug('Detected [%s] removed from state_functions in [%s]', k, self) # Determine the new reference count of state functions for the # class who the removed item was for. module, name, version = k key = (module, name) count = self._state_function_classes[key] - len(v) # Store the new reference count. Delete the entry if it is zero. if count < 0: logger.warn('Unexpectedly reached negative reference count ' + 'value of [%s] for [%s]', count, key) del self._state_function_classes[key] elif count == 0: del self._state_function_classes[key] else: self._state_function_classes[key] = count # Update our reference counts for changes to the list of functions # for a specific class and version. The 'changed' dictionary's values # are the old values. for k, v in event.changed.items(): value = self.state_functions[k] logger.debug('Detected [%s] changed in state_functions from ' + \ '[%s] to [%s] in [%s]', k, v, value, self) # Determine the new reference count as a result of the change. module, name, version = k key = (module, name) count = self._state_function_classes[key] - len(v) + len(value) # Store the new reference count. Delete the entry if it is zero. if count < 0: logger.warn('Unexpectedly reached negative reference count ' + 'value of [%s] for [%s]', count, key) del self._state_function_classes[key] elif count == 0: del self._state_function_classes[key] else: self._state_function_classes[key] = count # Update our reference counts for newly registered state functions. for k, v in event.added.items(): logger.debug('Detected mapping of [%s] to [%s] added to ' + \ 'state_functions in [%s]', k, v, self) # Determine the new reference count as a result of the change. module, name, version = k key = (module, name) count = self._state_function_classes.setdefault(key, 0) + len(v) # Store the new reference count self._state_function_classes[key] = count def _version_attribute_map_changed(self, old, new): logger.debug('Detected version_attribute_map change from [%s] ' + \ 'to [%s] in [%s]', old, new, self) def _version_attribute_map_items_changed(self, event): for o in event.removed: logger.debug('Detected [%s] removed from version_attribute_map ' + \ 'in [%s]', o, self) for o in event.changed: logger.debug('Detected [%s] changed in version_attribute_map ' + \ 'in [%s]', o, self) for o in event.added: logger.debug('Detected [%s] added to version_attribute_map in ' + \ '[%s]', o, self) ### EOF ###################################################################### apptools-4.1.0/apptools/sweet_pickle/README.txt0000644000175100001440000000047211674464005022365 0ustar ischnellusers00000000000000The sweet_pickle package is not in wide use and thus has not been fully vetted. As such, we reserve the right to make significant refactorings to this package, classes, and API. **** USE ONLY IF YOU ACCEPT THIS SITUATION. **** See the comments in the package init for an explanation of why this package exists. apptools-4.1.0/apptools/sweet_pickle/global_registry.py0000644000175100001440000000747311674464005024441 0ustar ischnellusers00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2006 by Enthought, Inc. # All rights reserved. # # Author: Dave Peterson # #----------------------------------------------------------------------------- """ Manages a singleton updater that acts as a global registry. Our goal is to enable the apptools.sweet_pickle framework to understand how pickled data should be treated during unpickling so that the resulting object hierarchy reflects the current versions of the object's classes -AND- that this should work no matter who is doing the unpickling. This requires a global registry since there is no way to know in advance what objects are contained within a given pickle file, and thus no way to gather the required information to know how to treat the pickle data during unpickling. For example, a pickle of an Envisage project may contain many custom classes, instantiated at the behest of various plugins, which have gone through various versionings and refactorings. But it is the project plugin that needs to unpickle these objects to 'load' a project, not the other plugins that added those custom class instances into the project. This registry is used by the apptools.sweet_pickle framework's unpickler only by default. That is, only if no updater was explicitly provided. It is important that users interact with the registry through the provided methods. If they do not, then the reference they receive will be the one that was in place at the time of the import which may or MAY NOT be the current repository due to the way this framework manages the repository. """ try: import thread as _thread except ImportError: import dummy_thread as _thread ############################################################################## # function 'get_global_registry' ############################################################################## def get_global_registry(): """ Returns the global registry in a manner that allows for lazy instantiation. """ global _global_registry, _global_registry_lock # Do we need to create the registry? if _global_registry is None: # We can only do this safely in a threaded situation by using a lock. # Note that the previous check for None doesn't guarantee we are # the only one trying to create an instance, so, we'll check for none # again once we acquire the lock and then only create the singleton # if there still isn't an instance. _global_registry_lock.acquire() try: if _global_registry is None: from updater import Updater _global_registry = Updater() finally: _global_registry_lock.release() return _global_registry ############################################################################## # private function '_clear_global_registry' ############################################################################## def _clear_global_registry(): """ Clears out the current global registry. This exists purely to allow testing of the global registry and the apptools.sweet_pickle framework. THIS METHOD SHOULD NEVER BE CALLED DURING NORMAL OPERATIONS! """ global _global_registry _global_registry = None ############################################################################## # private, but global, variables ############################################################################## # The global singleton updater _global_registry = None # The lock used to make access to the global singleton thread safe _global_registry_lock = _thread.allocate_lock() #### EOF ##################################################################### apptools-4.1.0/apptools/sweet_pickle/__init__.py0000644000175100001440000001671611674464005023010 0ustar ischnellusers00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2006 by Enthought, Inc. # All rights reserved. # # Author: Dave Peterson # #----------------------------------------------------------------------------- """ A pickle framework that supports unpickling of refactored versions of old classes. Part of the AppTools project of the Enthought Tool Suite. Beyond refactoring support, there are additional useful capabilities of this package: - HasTraits objects are fully pickled when sweet_pickle.dump() or sweet_pickle.dumps() are called. This means every trait's value is pickled explicitly. This allows improved self-consistency as values within unpickled objects correspond to the default trait values of the instance that was pickled rather than the default values of the current class definition. - Multiple updaters can be registered to update state for any version of a class. This is extremely useful when an instance being pickled and unpickled is the result of an addition of one or more Trait Categories to a class. In that case, the Category contributor can also register a state function to update the Category's traits through versioning. We have duplicated the API of the Python 'pickle' module within this package. Thus, users can simply import sweet_pickle in places where they previously used pickle or cPickle. For example:: import cPickle ---> import apptools.sweet_pickle as pickle s = pickle.dumps(obj) s = pickle.dumps(obj) pickle.loads(s) pickle.loads(s) Pickles generated by this package *can be* unpickled by standard pickle or cPickle, though you lose the benefit of the refactoring support during unpickling. As a result of the above, this package is a drop-in replacement for the Python 'pickle' module or 'cPickle' module and should be safe to use even if you never version or refactor your own classes. In fact, we STRONGLY RECOMMEND that you use this framework for all of your pickle needs because you never know when one of the classes you encounter during unpickling has been versioned or otherwise refactored by someone else. See module 'pickle' for more basic information about pickling. The most common way to benefit from the versioning capabilities of this framework is to register class mappings and state modification functions with the global updater registry (more detail is below.) However, you may also choose to explicitly instantiate an Unpickler and provide it with your own explicit definition of class mappings and state modification functions. We do not provide any help on the latter at this time. You can register class mappings and state modification functions with the global updater registry using the methods provided on the Updater instance that is the global registry. You can get this instance by calling the 'get_global_registry' function exposed through this package's namespace. This framework has been designed so that you can register your class mappings and state modification functions during the import of the module or package that contained the original class. However, you may also register your mappings and functions at any point such that the they are known to the framework prior to, or become known during the attempt to, unpickle the class they modify. The framework will call a __setstate__ method on the final target class of any unpickled instance. It will not call __setstate__ methods on any beginning or intermediate classes within a chain of class mappings. A class mapping is used to redirect the unpickling of one class to return an instantiation of another class. The classes can be in different modules, and the modules in different packages. Mappings can be chained. For example, given the mappings:: foo.bar.Bar --> foo.baz.Baz foo.baz.Baz --> foo.Foo An attempt to unpickle a foo.bar.Bar would actually generate a foo.Foo instance. A state modification function is called during the execution of the __setstate__ method during unpickling of an object of the type and version for which it was registered for. The function must accept a single argument which is a state dictionary and must then return the modified state dictionary. Additionally, the function should change the version variable within the state to represent the version the new state represents. (The framework will assume an increment of one if this is not done.) The framework ensures that state modification functions are chained appropriately to convert through multiple versions and/or class mappings. Note that refactorings that cause classes to be completed removed from the source code can be supported, without breaking unpickling of object hierarchies that include an instace of that class, by adding a mapping to the Placeholder class in the placeholder module. """ ############################################################################## # Implement the Python pickle package API ############################################################################## # Expose our custom pickler as the standard Unpickler from versioned_unpickler import VersionedUnpickler as Unpickler # Use our custom unpickler to load from files def load(file, max_pass=-1): return Unpickler(file).load(max_pass) # Use our custom unpickler to load from strings def loads(str, max_pass=-1): try: from cStringIO import StringIO except ImportError: from StringIO import StringIO file = StringIO(str) return Unpickler(file).load(max_pass) # We don't customize the Python pickler, though we do use the cPickle module # for improved performance. from cPickle import Pickler # Implement the dump and dumps methods so that all traits in a HasTraits object # get included in the pickle. def dump(obj, file, protocol=2): _flush_traits(obj) from cPickle import dump as d return d(obj, file, protocol) def dumps(obj, protocol=2): _flush_traits(obj) from cPickle import dumps as ds return ds(obj, protocol) # We don't customize exceptions so just map to the Python pickle package from pickle import PickleError, PicklingError, UnpicklingError ############################################################################## # Allow retrieval of the global registry ############################################################################## from global_registry import get_global_registry ############################################################################## # Expose our Updater class so users can explicitly create their own. ############################################################################## from updater import Updater ############################################################################## # Method to ensure that all traits on a has traits object are included in a # pickle. ############################################################################## def _flush_traits(obj): if hasattr(obj, 'trait_names'): for name, value in obj.traits().items(): if value.type == 'trait': try: getattr(obj, name) except AttributeError: pass ### EOF ###################################################################### apptools-4.1.0/apptools/sweet_pickle/placeholder.py0000644000175100001440000000163311674464005023523 0ustar ischnellusers00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2006 by Enthought, Inc. # All rights reserved. # # Author: Vibha Srinivasan # #----------------------------------------------------------------------------- """ An empty class that serves as a placeholder to map a class to when that class has been deleted as a result of a refactoring. """ # Enthought library imports from traits.api import HasTraits ############################################################################## # class 'PlaceHolder' ############################################################################## class PlaceHolder(HasTraits): """ An empty class that serves as a placeholder to map a class to when that class has been deleted as a result of a refactoring. """ ### EOF ###################################################################### apptools-4.1.0/apptools/io/0000755000175100001440000000000011674464005016615 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/io/tests/0000755000175100001440000000000011674464005017757 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/io/tests/file_test_case.py0000644000175100001440000001453311674464005023310 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Tests file operations. """ # Standard library imports. import os, shutil, stat, unittest # Enthought library imports. from apptools.io import File class FileTestCase(unittest.TestCase): """ Tests file operations on a local file system. """ ########################################################################### # 'TestCase' interface. ########################################################################### def setUp(self): """ Prepares the test fixture before each test method is called. """ try: shutil.rmtree('data') except: pass os.mkdir('data') return def tearDown(self): """ Called immediately after each test method has been called. """ shutil.rmtree('data') return ########################################################################### # Tests. ########################################################################### def test_properties(self): """ file properties """ # Properties of a non-existent file. f = File('data/bogus.xx') self.assert_(os.path.abspath(os.path.curdir) in f.absolute_path) self.assertEqual(f.children, None) self.assertEqual(f.ext, '.xx') self.assertEqual(f.exists, False) self.assertEqual(f.is_file, False) self.assertEqual(f.is_folder, False) self.assertEqual(f.is_package, False) self.assertEqual(f.is_readonly, False) self.assertEqual(f.mime_type, 'content/unknown') self.assertEqual(f.name, 'bogus') self.assertEqual(f.parent.path, 'data') self.assertEqual(f.path, 'data/bogus.xx') self.assert_(os.path.abspath(os.path.curdir) in f.url) self.assertEqual(str(f), 'File(%s)' % f.path) # Properties of an existing file. f = File('data/foo.py') f.create_file() self.assert_(os.path.abspath(os.path.curdir) in f.absolute_path) self.assertEqual(f.children, None) self.assertEqual(f.ext, '.py') self.assertEqual(f.exists, True) self.assertEqual(f.is_file, True) self.assertEqual(f.is_folder, False) self.assertEqual(f.is_package, False) self.assertEqual(f.is_readonly, False) self.assertEqual(f.mime_type, 'text/x-python') self.assertEqual(f.name, 'foo') self.assertEqual(f.parent.path, 'data') self.assertEqual(f.path, 'data/foo.py') self.assert_(os.path.abspath(os.path.curdir) in f.url) # Make it readonly. os.chmod(f.path, stat.S_IRUSR) self.assertEqual(f.is_readonly, True) # And then make it NOT readonly so that we can delete it at the end of # the test! os.chmod(f.path, stat.S_IRUSR | stat.S_IWUSR) self.assertEqual(f.is_readonly, False) return def test_copy(self): """ file copy """ content = 'print "Hello World!"\n' f = File('data/foo.py') self.assertEqual(f.exists, False) # Create the file. f.create_file(content) self.assertEqual(f.exists, True) self.failUnlessRaises(ValueError, f.create_file, content) self.assertEqual(f.children, None) self.assertEqual(f.ext, '.py') self.assertEqual(f.is_file, True) self.assertEqual(f.is_folder, False) self.assertEqual(f.mime_type, 'text/x-python') self.assertEqual(f.name, 'foo') self.assertEqual(f.path, 'data/foo.py') # Copy the file. g = File('data/bar.py') self.assertEqual(g.exists, False) f.copy(g) self.assertEqual(g.exists, True) self.assertEqual(g.children, None) self.assertEqual(g.ext, '.py') self.assertEqual(g.is_file, True) self.assertEqual(g.is_folder, False) self.assertEqual(g.mime_type, 'text/x-python') self.assertEqual(g.name, 'bar') self.assertEqual(g.path, 'data/bar.py') # Attempt to copy a non-existent file (should do nothing). f = File('data/bogus.xx') self.assertEqual(f.exists, False) g = File('data/bogus_copy.py') self.assertEqual(g.exists, False) f.copy(g) self.assertEqual(g.exists, False) return def test_create_file(self): """ file creation """ content = 'print "Hello World!"\n' f = File('data/foo.py') self.assertEqual(f.exists, False) # Create the file. f.create_file(content) self.assertEqual(f.exists, True) self.assertEqual(file(f.path).read(), content) # Try to create it again. self.failUnlessRaises(ValueError, f.create_file, content) return def test_delete(self): """ file deletion """ content = 'print "Hello World!"\n' f = File('data/foo.py') self.assertEqual(f.exists, False) # Create the file. f.create_file(content) self.assertEqual(f.exists, True) self.failUnlessRaises(ValueError, f.create_file, content) self.assertEqual(f.children, None) self.assertEqual(f.ext, '.py') self.assertEqual(f.is_file, True) self.assertEqual(f.is_folder, False) self.assertEqual(f.mime_type, 'text/x-python') self.assertEqual(f.name, 'foo') self.assertEqual(f.path, 'data/foo.py') # Delete it. f.delete() self.assertEqual(f.exists, False) # Attempt to delete a non-existet file (should do nothing). f = File('data/bogus.py') self.assertEqual(f.exists, False) f.delete() self.assertEqual(f.exists, False) return if __name__ == "__main__": unittest.main() #### EOF ###################################################################### apptools-4.1.0/apptools/io/tests/folder_test_case.py0000644000175100001440000001722011674464005023640 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Tests folder operations. """ # Standard library imports. import os, shutil, stat, unittest from os.path import join # Enthought library imports. from apptools.io import File class FolderTestCase(unittest.TestCase): """ Tests folder operations on a local file system. """ ########################################################################### # 'TestCase' interface. ########################################################################### def setUp(self): """ Prepares the test fixture before each test method is called. """ try: shutil.rmtree('data') except: pass os.mkdir('data') return def tearDown(self): """ Called immediately after each test method has been called. """ shutil.rmtree('data') return ########################################################################### # Tests. ########################################################################### def test_properties(self): """ folder properties """ # Properties of a non-existent folder. f = File('data/bogus') self.assert_(os.path.abspath(os.path.curdir) in f.absolute_path) self.assertEqual(f.children, None) self.assertEqual(f.ext, '') self.assertEqual(f.exists, False) self.assertEqual(f.is_file, False) self.assertEqual(f.is_folder, False) self.assertEqual(f.is_package, False) self.assertEqual(f.is_readonly, False) self.assertEqual(f.mime_type, 'content/unknown') self.assertEqual(f.name, 'bogus') self.assertEqual(f.parent.path, 'data') self.assertEqual(f.path, 'data/bogus') self.assert_(os.path.abspath(os.path.curdir) in f.url) self.assertEqual(str(f), 'File(%s)' % f.path) # Properties of an existing folder. f = File('data/sub') f.create_folder() self.assert_(os.path.abspath(os.path.curdir) in f.absolute_path) self.assertEqual(len(f.children), 0) self.assertEqual(f.ext, '') self.assertEqual(f.exists, True) self.assertEqual(f.is_file, False) self.assertEqual(f.is_folder, True) self.assertEqual(f.is_package, False) self.assertEqual(f.is_readonly, False) self.assertEqual(f.mime_type, 'content/unknown') self.assertEqual(f.name, 'sub') self.assertEqual(f.parent.path, 'data') self.assertEqual(f.path, 'data/sub') self.assert_(os.path.abspath(os.path.curdir) in f.url) # Make it readonly. os.chmod(f.path, stat.S_IRUSR) self.assertEqual(f.is_readonly, True) # And then make it NOT readonly so that we can delete it at the end of # the test! os.chmod(f.path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) self.assertEqual(f.is_readonly, False) # Properties of a Python package folder. f = File('data/package') f.create_folder() init = File('data/package/__init__.py') init.create_file() self.assert_(os.path.abspath(os.path.curdir) in f.absolute_path) self.assertEqual(len(f.children), 1) self.assertEqual(f.ext, '') self.assertEqual(f.exists, True) self.assertEqual(f.is_file, False) self.assertEqual(f.is_folder, True) self.assertEqual(f.is_package, True) self.assertEqual(f.is_readonly, False) self.assertEqual(f.mime_type, 'content/unknown') self.assertEqual(f.name, 'package') self.assertEqual(f.parent.path, 'data') self.assertEqual(f.path, 'data/package') self.assert_(os.path.abspath(os.path.curdir) in f.url) return def test_copy(self): """ folder copy """ f = File('data/sub') self.assertEqual(f.exists, False) # Create the folder. f.create_folder() self.assertEqual(f.exists, True) self.failUnlessRaises(ValueError, f.create_folder) self.assertEqual(len(f.children), 0) self.assertEqual(f.ext, '') self.assertEqual(f.is_file, False) self.assertEqual(f.is_folder, True) self.assertEqual(f.mime_type, 'content/unknown') self.assertEqual(f.name, 'sub') self.assertEqual(f.path, 'data/sub') # Copy the folder. g = File('data/copy') self.assertEqual(g.exists, False) f.copy(g) self.assertEqual(g.exists, True) self.assertEqual(len(g.children), 0) self.assertEqual(g.ext, '') self.assertEqual(g.is_file, False) self.assertEqual(g.is_folder, True) self.assertEqual(g.mime_type, 'content/unknown') self.assertEqual(g.name, 'copy') self.assertEqual(g.path, 'data/copy') # Attempt to copy a non-existent folder (should do nothing). f = File('data/bogus') self.assertEqual(f.exists, False) g = File('data/bogus_copy') self.assertEqual(g.exists, False) f.copy(g) self.assertEqual(g.exists, False) return def test_create_folder(self): """ folder creation """ f = File('data/sub') self.assertEqual(f.exists, False) # Create the folder. f.create_folder() self.assertEqual(f.exists, True) parent = File('data') self.assertEqual(len(parent.children), 1) self.assertEqual(parent.children[0].path, join('data', 'sub')) # Try to create it again. self.failUnlessRaises(ValueError, f.create_folder) return def test_create_folders(self): """ nested folder creation """ f = File('data/sub/foo') self.assertEqual(f.exists, False) # Attempt to create the folder with 'create_folder' which requires # that all intermediate folders exist. self.failUnlessRaises(OSError, f.create_folder) # Create the folder. f.create_folders() self.assertEqual(f.exists, True) self.assertEqual(File('data/sub').exists, True) # Try to create it again. self.failUnlessRaises(ValueError, f.create_folders) return def test_delete(self): """ folder deletion """ f = File('data/sub') self.assertEqual(f.exists, False) # Create the folder. f.create_folder() self.assertEqual(f.exists, True) self.failUnlessRaises(ValueError, f.create_folder) self.assertEqual(len(f.children), 0) self.assertEqual(f.ext, '') self.assertEqual(f.is_file, False) self.assertEqual(f.is_folder, True) self.assertEqual(f.mime_type, 'content/unknown') self.assertEqual(f.name, 'sub') self.assertEqual(f.path, 'data/sub') # Delete it. f.delete() self.assertEqual(f.exists, False) # Attempt to delete a non-existet folder (should do nothing). f = File('data/bogus') self.assertEqual(f.exists, False) f.delete() self.assertEqual(f.exists, False) return #### EOF ###################################################################### apptools-4.1.0/apptools/io/api.py0000644000175100001440000000122211674464005017735 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ from file import File apptools-4.1.0/apptools/io/file.py0000644000175100001440000002313511674464005020112 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A representation of files and folders in a file system. """ # Standard/built-in imports. import mimetypes, os, shutil, stat # Enthought library imports. from traits.api import Bool, HasPrivateTraits, Instance, List, Property from traits.api import Str class File(HasPrivateTraits): """ A representation of files and folders in a file system. """ #### 'File' interface ##################################################### # The absolute path name of this file/folder. absolute_path = Property(Str) # The folder's children (for files this is always None). children = Property(List('File')) # The file extension (for folders this is always the empty string). # # fixme: Currently the extension includes the '.' (ie. we have '.py' and # not 'py'). This is because things like 'os.path.splitext' leave the '.' # on, but I'm not sure that this is a good idea! ext = Property(Str) # Does the file/folder exist? exists = Property(Bool) # Is this an existing file? is_file = Property(Bool) # Is this an existing folder? is_folder = Property(Bool) # Is this a Python package (ie. a folder contaning an '__init__.py' file. is_package = Property(Bool) # Is the file/folder readonly? is_readonly = Property(Bool) # The MIME type of the file (for a folder this will always be # 'context/unknown' (is that what it should be?)). mime_type = Property(Str) # The last component of the path without the extension. name = Property(Str) # The parent of this file/folder (None if it has no parent). parent = Property(Instance('File')) # The path name of this file/folder. path = Str # A URL reference to the file. url = Property(Str) ########################################################################### # 'object' interface. ########################################################################### def __init__(self, path, **traits): """ Creates a new representation of the specified path. """ super(File, self).__init__(path=path, **traits) return def __cmp__(self, other): """ Comparison operators. """ if isinstance(other, File): return cmp(self.path, other.path) return 1 def __str__(self): """ Returns an 'informal' string representation of the object. """ return 'File(%s)' % self.path ########################################################################### # 'File' interface. ########################################################################### #### Properties ########################################################### def _get_absolute_path(self): """ Returns the absolute path of this file/folder. """ return os.path.abspath(self.path) def _get_children(self): """ Returns the folder's children. Returns None if the path does not exist or is not a folder. """ if self.is_folder: children = [] for name in os.listdir(self.path): children.append(File(os.path.join(self.path, name))) else: children = None return children def _get_exists(self): """ Returns True if the file exists, otherwise False. """ return os.path.exists(self.path) def _get_ext(self): """ Returns the file extension. """ name, ext = os.path.splitext(self.path) return ext def _get_is_file(self): """ Returns True if the path exists and is a file. """ return self.exists and os.path.isfile(self.path) def _get_is_folder(self): """ Returns True if the path exists and is a folder. """ return self.exists and os.path.isdir(self.path) def _get_is_package(self): """ Returns True if the path exists and is a Python package. """ return self.is_folder and '__init__.py' in os.listdir(self.path) def _get_is_readonly(self): """ Returns True if the file/folder is readonly, otherwise False. """ # If the File object is a folder, os.access cannot be used because it # returns True for both read-only and writable folders on Windows # systems. if self.is_folder: # Mask for the write-permission bits on the folder. If these bits # are set to zero, the folder is read-only. WRITE_MASK = 0x92 permissions = os.stat(self.path)[0] if permissions & WRITE_MASK == 0: readonly = True else: readonly = False elif self.is_file: readonly = not os.access(self.path, os.W_OK) else: readonly = False return readonly def _get_mime_type(self): """ Returns the mime-type of this file/folder. """ mime_type, encoding = mimetypes.guess_type(self.path) if mime_type is None: mime_type = "content/unknown" return mime_type def _get_name(self): """ Returns the last component of the path without the extension. """ basename = os.path.basename(self.path) name, ext = os.path.splitext(basename) return name def _get_parent(self): """ Returns the parent of this file/folder. """ return File(os.path.dirname(self.path)) def _get_url(self): """ Returns the path as a URL. """ return 'file://%s' % self.absolute_path #### Methods ############################################################## def copy(self, destination): """ Copies this file/folder. """ # Allow the destination to be a string. if not isinstance(destination, File): destination = File(destination) if self.is_folder: shutil.copytree(self.path, destination.path) elif self.is_file: shutil.copyfile(self.path, destination.path) return def create_file(self, contents=''): """ Creates a file at this path. """ if self.exists: raise ValueError("file %s already exists" % self.path) f = file(self.path, 'w') f.write(contents) f.close() return def create_folder(self): """ Creates a folder at this path. All intermediate folders MUST already exist. """ if self.exists: raise ValueError("folder %s already exists" % self.path) os.mkdir(self.path) return def create_folders(self): """ Creates a folder at this path. This will attempt to create any missing intermediate folders. """ if self.exists: raise ValueError("folder %s already exists" % self.path) os.makedirs(self.path) return def create_package(self): """ Creates a package at this path. All intermediate folders/packages MUST already exist. """ if self.exists: raise ValueError("package %s already exists" % self.path) os.mkdir(self.path) # Create the '__init__.py' file that actually turns the folder into a # package! init = File(os.path.join(self.path, '__init__.py')) init.create_file() return def delete(self): """ Deletes this file/folder. Does nothing if the file/folder does not exist. """ if self.is_folder: # Try to make sure that everything in the folder is writeable. self.make_writeable() # Delete it! shutil.rmtree(self.path) elif self.is_file: # Try to make sure that the file is writeable. self.make_writeable() # Delete it! os.remove(self.path) return def make_writeable(self): """ Attempt to make the file/folder writeable. """ if self.is_folder: # Try to make sure that everything in the folder is writeable # (i.e., can be deleted!). This comes in especially handy when # deleting '.svn' directories. for path, dirnames, filenames in os.walk(self.path): for name in dirnames + filenames: filename = os.path.join(path, name) if not os.access(filename, os.W_OK): os.chmod(filename, stat.S_IWUSR) elif self.is_file: # Try to make sure that the file is writeable (i.e., can be # deleted!). if not os.access(self.path, os.W_OK): os.chmod(self.path, stat.S_IWUSR) return def move(self, destination): """ Moves this file/folder. """ # Allow the destination to be a string. if not isinstance(destination, File): destination = File(destination) # Try to make sure that everything in the directory is writeable. self.make_writeable() # Move it! shutil.move(self.path, destination.path) return #### EOF ###################################################################### apptools-4.1.0/apptools/io/__init__.py0000644000175100001440000000142411674464005020727 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Provides an abstraction for files and folders in a file system. Part of the AppTools project of the Enthought Tool Suite. """ from api import * apptools-4.1.0/apptools/template/0000755000175100001440000000000011674464005020021 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/template/impl/0000755000175100001440000000000011674464005020762 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/template/impl/any_context_data_name_item.py0000644000175100001440000001245011674464005026700 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # An abstract base class implementation of the ITemplateDataNameItem interface # that looks for specified sub-contexts in its input context and if one match # is found, outputs that context; otherwise if more than one match is found it # outputs a context containing all matching sub-contexts found. # # Written by: David C. Morrill # # Date: 07/29/2007 # # (c) Copyright 2007 by Enthought, Inc. # #------------------------------------------------------------------------------- """ An abstract base class implementation of the ITemplateDataNameItem interface that looks for specified sub-contexts in its input context and if one match is found, outputs that context; otherwise if more than one match is found it outputs a context containing all matching sub-contexts found. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- from traits.api \ import HasPrivateTraits, Instance, Property, implements, on_trait_change from apptools.template.itemplate_data_context \ import ITemplateDataContext from apptools.template.itemplate_data_name_item \ import ITemplateDataNameItem from apptools.template.template_impl \ import Template from template_data_context \ import TemplateDataContext #------------------------------------------------------------------------------- # 'AnyContextDataNameItem' class: #------------------------------------------------------------------------------- class AnyContextDataNameItem ( Template ): """ An abstract base class implementation of the ITemplateDataNameItem interface that looks for specified sub-contexts in its input context and if one match is found, outputs that context; otherwise if more than one match is found it outputs a context containing all matching sub-contexts found. """ implements ( ITemplateDataNameItem ) #-- 'ITemplateDataNameItem' Interface Implementation ----------------------- # The data context which this data name item should match against: input_data_context = Instance( ITemplateDataContext ) # The data context containing the data values and/or contexts this data # name item matches: output_data_context = Instance( ITemplateDataContext ) # The ITemplateChoice instance representing the current settings of the # data name item. This value must be read/write, and must be overridden by # sublasses. data_name_item_choice = Property # The alternative choices the user has for the data name item settings for # the current input data context. The list may be empty, in which case the # user cannot change the settings of the data name item. This value can be # read only, and must be overridden by subclasses. data_name_item_choices = Property #-- Private Traits --------------------------------------------------------- # The current input data context: current_input_data_context = Property #-- Partially Abstract Methods (Can be overridden in subclasses) ----------- def filter ( self, name, context ): """ Returns **True** if the specified *context* called *name* should be included in the output context; and **False** otherwise. """ return False #-- Property Implementations ----------------------------------------------- def _get_data_name_item_choice ( self ): raise NotImplementedError def _set_data_name_item_choice ( self, value ): raise NotImplementedError def _get_data_name_item_choices ( self ): raise NotImplementedError def _get_current_input_data_context ( self ): return self.input_data_context #-- Trait Event Handlers --------------------------------------------------- def _input_data_context_changed ( self ): """ Handles the 'input_data_context' trait being changed. """ self.inputs_changed() #-- Private Methods -------------------------------------------------------- def inputs_changed ( self ): """ Handles any of the input values being changed. May be called by subclasses. """ output_context = None input_context = self.input_data_context if input_context is not None: contexts = {} # Process each name/context in the input data contexts, and only add # those that match the subclass's filter to the output context: filter = self.filter gdc = input_context.get_data_context for name in input_context.data_contexts: if filter( name, gdc( name ) ): contexts[ name ] = context # If the result set is not empty, create an output context for it: n = len( contexts ) if n == 1: output_context = values.values()[0] elif n > 1: output_context = TemplateDataContext( data_context_path = input_context.data_context_path, data_context_name = input_context.data_context_name, contexts = contexts ) # Set the new output context: self.output_data_context = output_context apptools-4.1.0/apptools/template/impl/api.py0000644000175100001440000000147611674464005022115 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Publically exported symbols for the template.impl package. # # Written by: David C. Morrill # # Date: 07/29/2007 # # (c) Copyright 2007 by Enthought, Inc. # #------------------------------------------------------------------------------- from any_context_data_name_item \ import AnyContextDataNameItem from any_data_name_item \ import AnyDataNameItem from context_data_name_item \ import ContextDataNameItem from template_data_context \ import TemplateDataContext from template_data_source \ import TemplateDataSource from value_data_name_item \ import ValueDataNameItem from value_nd_data_name_item \ import ValueNDDataNameItem, Value1DDataNameItem, Value2DDataNameItem, \ Value3DDataNameItem apptools-4.1.0/apptools/template/impl/context_data_name_item.py0000644000175100001440000000653311674464005026036 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # A concrete implementation of the ITemplateDataNameItem interface that looks # for a specified sub-context in its input context and outputs that as its # output context if it is found. # # Written by: David C. Morrill # # Date: 07/29/2007 # # (c) Copyright 2007 by Enthought, Inc. # #------------------------------------------------------------------------------- """ A concrete implementation of the ITemplateDataNameItem interface that looks for a specified sub-context in its input context and outputs that as its output context if it is found. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- from apptools.template.template_traits \ import TStr from any_context_data_name_item \ import AnyContextDataNameItem from helper \ import path_for, parse_name #------------------------------------------------------------------------------- # 'ContextDataNameItem' class: #------------------------------------------------------------------------------- class ContextDataNameItem ( AnyContextDataNameItem ): """ A concrete implementation of the ITemplateDataNameItem interface that looks for a specified sub-context in its input context and outputs that as its output context if it is found. """ #-- Public Traits ---------------------------------------------------------- # The name of the context to be matched: name = TStr #-- Abstract Method Implementations ---------------------------------------- def filter ( self, name, context ): """ Returns **True** if the specified *context* called *name* should be included in the output context; and **False** otherwise. """ return (name == self.name_last) #-- AnyDataNameItem Property Implementation Overrides ---------------------- def _get_data_name_item_choice ( self ): return TemplateChoice( choice_value = self.name ) def _set_data_name_item_choice ( self, value ): self.name = value.choice_value def _get_data_name_item_choices ( self ): return self._get_choices( self.input_data_context ) def _get_current_input_data_context ( self ): context = self.input_data_context for name in parse_name( self.name )[:-1]: if name not in context.data_contexts: return None context = context.get_data_context( name ) return context #-- Trait Event Handlers --------------------------------------------------- def _name_changed ( self, name ): """ Handles the 'name' trait being changed. """ self.name_last = parse_name( name )[-1] self.inputs_changed() #-- Private Methods -------------------------------------------------------- def _get_choices ( self, context, path = '' ): """ Returns all of the valid TemplateChoices for this item. """ choices = [] gdc = context.get_data_context for name in context.data_contexts: next_path = path_for( path, name ) choices.append( TemplateChoice( choice_value = next_path ) ) choices.extend( self._get_choices( gdc( name ), next_path ) ) return choices apptools-4.1.0/apptools/template/impl/template_data_context.py0000644000175100001440000001061611674464005025710 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # A concrete implementation of the ITemplateDataContext interface intended to # be used for creating the *output_data_context* value of an # **ITemplateDataNameItem** implementation (although they are not required to # use it). # # Written by: David C. Morrill # # Date: 07/29/2007 # # (c) Copyright 2007 by Enthought, Inc. # #------------------------------------------------------------------------------- """ A concrete implementation of the ITemplateDataContext interface intended to be used for creating the *output_data_context* value of an **ITemplateDataNameItem** implementation (although they are not required to use it). """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- from traits.api\ import HasPrivateTraits, Dict, Str, Any, Property, implements, \ cached_property from apptools.template.itemplate_data_context \ import ITemplateDataContext, ITemplateDataContextError #------------------------------------------------------------------------------- # 'TemplateDataContext' class: #------------------------------------------------------------------------------- class TemplateDataContext ( HasPrivateTraits ): """ A concrete implementation of the ITemplateDataContext interface intended to be used for creating the *output_data_context* value of an **ITemplateDataNameItem** implementation (although they are not required to use it). """ implements( ITemplateDataContext ) #-- 'ITemplateDataContext' Interface Traits -------------------------------- # The path to this data context (does not include the 'data_context_name'): data_context_path = Str # The name of the data context: data_context_name = Str # A list of the names of the data values in this context: data_context_values = Property # List( Str ) # The list of the names of the sub-contexts of this context: data_contexts = Property # List( Str ) #-- Public Traits --------------------------------------------------------- # The data context values dictionary: values = Dict( Str, Any ) # The data contexts dictionary: contexts = Dict( Str, ITemplateDataContext ) #-- 'ITemplateDataContext' Property Implementations ------------------------ @cached_property def _get_data_context_values ( self ): values = self.values.keys() values.sort() return values @cached_property def _get_data_contexts ( self ): contexts = self.contexts.keys() contexts.sort() return contexts #-- 'ITemplateDataContext' Interface Implementation ------------------------ def get_data_context_value ( self, name ): """ Returns the data value with the specified *name*. Raises a **ITemplateDataContextError** if *name* is not defined as a data value in the context. Parameters ---------- name : A string specifying the name of the context data value to be returned. Returns ------- The data value associated with *name* in the context. The type of the data is application dependent. Raises **ITemplateDataContextError** if *name* is not associated with a data value in the context. """ try: return self.values[ name ] except: raise ITemplateDataContextError( "Value '%s' not found." % name ) def get_data_context ( self, name ): """ Returns the **ITemplateDataContext** value associated with the specified *name*. Raises **ITemplateDataContextError** if *name* is not defined as a data context in the context. Parameters ---------- name : A string specifying the name of the data context to be returned. Returns ------- The **ITemplateDataContext** associated with *name* in the context. Raises **ITemplateDataContextError** if *name* is not associated with a data context in the context. """ try: return self.context[ name ] except: raise ITemplateDataContextError( "Context '%s' not found." % name ) apptools-4.1.0/apptools/template/impl/template_data_source.py0000644000175100001440000000332211674464005025520 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # A concrete implementation of the ITemplateDataSource interface based on the # implementation of the TemplateDataName class. # # Write by: David C. Morrill # # Date: 07/30/2007 # # (c) Copyright 2007 by Enthought, Inc. # #------------------------------------------------------------------------------- """ A concrete implementation of the ITemplateDataSource interface based on the implementation of the TemplateDataName class. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- from traits.api \ import implements from apptools.template.template_data_name \ import TemplateDataName from apptools.template.itemplate_data_source \ import ITemplateDataSource #------------------------------------------------------------------------------- # 'TemplateDataSource' class: #------------------------------------------------------------------------------- class TemplateDataSource ( TemplateDataName ): """ A concrete implementation of the ITemplateDataSource interface based on the implementation of the TemplateDataName class. """ implements( ITemplateDataSource ) #-- ITemplateDataSource Interface Implementation --------------------------- def name_from_data_source ( self ): """ Allows the object to provide a description of the possibly optional data binding it requires. Returns ------- A **TemplateDataName** object describing the binding the data source object requires. """ return self apptools-4.1.0/apptools/template/impl/value_nd_data_name_item.py0000644000175100001440000000663611674464005026153 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # A concrete implementation of the ITemplateDataNameItem interface that looks # for array values of a specified dimensionality in its input context or # optionally in any of its sub-contexts and outputs a context containing # only those values that it found. # # Written by: David C. Morrill # # Date: 07/29/2007 # # (c) Copyright 2007 by Enthought, Inc. # #------------------------------------------------------------------------------- """ A concrete implementation of the ITemplateDataNameItem interface that looks for array values of a specified dimensionality in its input context or optionally in any of its sub-contexts and outputs a context containing only those values that it found. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- from numpy \ import array from apptools.template.template_traits \ import TRange from apptools.template.template_choice \ import TemplateChoice from any_data_name_item \ import AnyDataNameItem #------------------------------------------------------------------------------- # 'ValueNDDataNameItem' class: #------------------------------------------------------------------------------- class ValueNDDataNameItem ( AnyDataNameItem ): """ A concrete implementation of the ITemplateDataNameItem interface that looks for array values of a specified dimensionality in its input context or optionally in any of its sub-contexts and outputs a context containing only those values that it found. """ #-- Public Traits ---------------------------------------------------------- # The dimensionality of the array values to be accepted: dimensions = TRange( 0, 1000 ) #-- AnyDataNameItem Property Implementation Overrides ---------------------- def _get_data_name_item_choice ( self ): return TemplateChoice( choice_value = '%dD array' % self.dimensions ) def _set_data_name_item_choice ( self, value ): pass def _get_data_name_item_choices ( self ): return [] #-- Abstract Method Implementations ---------------------------------------- def filter ( self, name, value ): """ Returns **True** if the specified context data *name* and *value* should be included in the output context; and **False** otherwise. """ return (isinstance( value, array ) and (len( value.shape ) == self.dimensions )) #-- Trait Event Handlers --------------------------------------------------- def _name_changed ( self ): """ Handles the 'name' trait being changed. """ self.inputs_changed() #------------------------------------------------------------------------------- # Define a few common sub-classes for 1D, 2D and 3D arrays: #------------------------------------------------------------------------------- class Value1DDataNameItem ( ValueNDDataNameItem ): # Override the dimensionaly of the array values to be accepted: dimensions = 1 class Value2DDataNameItem ( ValueNDDataNameItem ): # Override the dimensionaly of the array values to be accepted: dimensions = 2 class Value3DDataNameItem ( ValueNDDataNameItem ): # Override the dimensionaly of the array values to be accepted: dimensions = 3 apptools-4.1.0/apptools/template/impl/helper.py0000644000175100001440000000207311674464005022615 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Helper functions/classes useful for implementing various template interfaces. # # Written by: David C. Morrill # # Date: 07/29/2007 # # (c) Copyright 2007 by Enthought, Inc. # #------------------------------------------------------------------------------- #------------------------------------------------------------------------------- # Returns a properly joined path name: #------------------------------------------------------------------------------- def path_for ( *names ): """ Returns a properly joined path name (i.e. the elements of the name are separated by '.'). """ return '.'.join( [ name for name in names if name != '' ] ) #------------------------------------------------------------------------------- # Parses a possible compound data context name: #------------------------------------------------------------------------------- def parse_name ( name ): """ Parses a possible compound data context name. """ return name.split( '.' ) apptools-4.1.0/apptools/template/impl/i_context_adapter.py0000644000175100001440000001107311674464005025032 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Defines an adapter from an codetools.contexts.api.IContext # to an ITemplateDataContext. # # Written by: David C. Morrill # Modified by: Robert Kern # # Date: 11/16/2007 # # (c) Copyright 2007 by Enthought, Inc. # #------------------------------------------------------------------------------- """ Defines an adapter from an codetools.contexts.api.IContext to an ITemplateDataContext. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- from traits.api \ import Adapter, Str, List, adapts from traits.protocols.api \ import AdaptationError, adapt from codetools.contexts.api \ import IContext from apptools.template.itemplate_data_context \ import ITemplateDataContext, ITemplateDataContextError from helper \ import path_for #------------------------------------------------------------------------------- # 'IContextAdapter' class: #------------------------------------------------------------------------------- class IContextAdapter ( Adapter ): """ Defines an adapter from an codetools.contexts.api.IContext to an ITemplateDataContext. """ adapts( IContext, ITemplateDataContext ) #-- ITemplateDataContext Interface Implementation -------------------------- # The path to this data context (does not include the 'data_context_name'): data_context_path = Str # The name of the data context: data_context_name = Str # A list of the names of the data values in this context: data_context_values = List( Str ) # The list of the names of the sub-contexts of this context: data_contexts = List( Str ) def get_data_context_value ( self, name ): """ Returns the data value with the specified *name*. Raises a **ITemplateDataContextError** if *name* is not defined as a data value in the context. Parameters ---------- name : A string specifying the name of the context data value to be returned. Returns ------- The data value associated with *name* in the context. The type of the data is application dependent. Raises **ITemplateDataContextError** if *name* is not associated with a data value in the context. """ try: if name in self.data_context_values: return self.adaptee[ name ] raise ITemplateDataContextError( "No value named '%s' found." % name ) except Exception, excp: raise ITemplateDataContextError( str( excp ) ) def get_data_context ( self, name ): """ Returns the **ITemplateDataContext** value associated with the specified *name*. Raises **ITemplateDataContextError** if *name* is not defined as a data context in the context. Parameters ---------- name : A string specifying the name of the data context to be returned. Returns ------- The **ITemplateDataContext** associated with *name* in the context. Raises **ITemplateDataContextError** if *name* is not associated with a data context in the context. """ try: if name in self.data_contexts: bdca = IContextAdapter( self.adaptee[ name ] ) bdca.data_context_path = path_for( self.data_context_path, self.data_context_name ) return bdca raise ITemplateDataContextError( "No context named '%s' found." % name ) except Exception, excp: raise ITemplateDataContextError( str( excp ) ) #-- Traits Event Handlers -------------------------------------------------- def _adaptee_changed ( self, context ): """ Handles being bound to a IContext object. """ self.data_context_name = context.name values = [] contexts = [] for name in context.keys(): value = context[ name ] try: adapt( value, IContext ) except AdaptationError: # Is not a subcontext. values.append( name ) else: # Is a subcontext. contexts.append( name ) self.data_context_values = values self.data_contexts = contexts apptools-4.1.0/apptools/template/impl/value_data_name_item.py0000644000175100001440000001260511674464005025463 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # A concrete implementation of the ITemplateDataNameItem interface that looks # for a specified named value in its input context or optionally in any of its # sub-contexts and outputs a context containing only those values that match # the specified name. # # Written by: David C. Morrill # # Date: 07/29/2007 # # (c) Copyright 2007 by Enthought, Inc. # #------------------------------------------------------------------------------- """ A concrete implementation of the ITemplateDataNameItem interface that looks for a specified named value in its input context or optionally in any of its sub-contexts and outputs a context containing only those values that match the specified name. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- from traits.api \ import Bool from apptools.template.template_traits \ import TStr, TBool from apptools.template.template_choice \ import TemplateChoice from apptools.template.itemplate_data_context \ import ITemplateDataContext from any_data_name_item \ import AnyDataNameItem from helper \ import parse_name, path_for #------------------------------------------------------------------------------- # Constants: #------------------------------------------------------------------------------- # Value used to reset to the factory settings: ResetChoice = '----' #------------------------------------------------------------------------------- # 'ValueDataNameItem' class: #------------------------------------------------------------------------------- class ValueDataNameItem ( AnyDataNameItem ): """ A concrete implementation of the ITemplateDataNameItem interface that looks for a specified named value in its input context or optionally in any of its sub-contexts and outputs a context containing only those values that match the specified name. """ #-- Public Traits ---------------------------------------------------------- # The name of the value to be matched: name = TStr # (Override) Should included sub-contexts be flattened into a single # context? flatten = True #-- Private Traits --------------------------------------------------------- # The current name of the value to be matched: current_name = TStr # The current name's last component: current_name_last = TStr # Should all possible choices be included? all_choices = Bool( True ) #-- AnyDataNameItem Property Implementation Overrides ---------------------- def _get_data_name_item_choice ( self ): return TemplateChoice( choice_value = self.current_name ) def _set_data_name_item_choice ( self, choice ): if choice.choice_value == ResetChoice: self.current_recursive = self.recursive self.current_name = self.name self.all_choices = True else: self.current_recursive = False self.current_name = choice.choice_value self.all_choices = False def _get_data_name_item_choices ( self ): context = self.input_data_context if context is None: return [] if not self.all_choices: output_context = self.output_data_context if output_context is not None: return [ TemplateChoice( choice_value = name ) for name in output_context.data_context_values ] return ([ TemplateChoice( choice_value = ResetChoice ) ] + self._get_choices( context )) def _get_current_input_data_context ( self ): context = self.input_data_context for name in parse_name( self.current_name )[:-1]: if name not in context.data_contexts: return None context = context.get_data_context( name ) return context #-- Abstract Method Implementations ---------------------------------------- def filter ( self, name, value ): """ Returns **True** if the specified context data *name* and *value* should be included in the output context; and **False** otherwise. """ return (name == self.current_name_last) #-- Trait Event Handlers --------------------------------------------------- def _name_changed ( self, name ): """ Handles the 'name' trait being changed. """ self.current_name = name def _current_name_changed ( self, name ): self.current_name_last = parse_name( name )[-1] self.inputs_changed() #-- Private Methods -------------------------------------------------------- def _get_choices ( self, context, path = '' ): """ Returns the list of available user settings choices for the specified context. """ choices = [ TemplateChoice( choice_value = path_for( path, name ) ) for name in context.data_context_values ] if self.recursive: # Now process all of the context's sub-contexts: gdc = context.get_data_context for name in context.data_contexts: choices.extend( self._get_choices( gdc( name ), path_for( path, context.data_context_name ) ) ) return choices apptools-4.1.0/apptools/template/impl/__init__.py0000644000175100001440000000000111674464005023062 0ustar ischnellusers00000000000000 apptools-4.1.0/apptools/template/impl/any_data_name_item.py0000644000175100001440000001736611674464005025147 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # An abstract base class implementation of the ITemplateDataNameItem interface # that looks for all specified values in its input context or optionally any of # its sub-contexts and outputs a context containing all such values found. # # Written by: David C. Morrill # # Date: 07/29/2007 # # (c) Copyright 2007 by Enthought, Inc. # #------------------------------------------------------------------------------- """ An abstract base class implementation of the ITemplateDataNameItem interface that looks for all specified values in its input context or optionally any of its sub-contexts and outputs a context containing all such values found. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- from traits.api \ import HasPrivateTraits, Instance, Property, implements from apptools.template.template_traits \ import TBool from apptools.template.itemplate_data_context \ import ITemplateDataContext from apptools.template.itemplate_data_name_item \ import ITemplateDataNameItem from apptools.template.template_impl \ import Template from template_data_context \ import TemplateDataContext from helper \ import path_for #------------------------------------------------------------------------------- # 'AnyDataNameItem' class: #------------------------------------------------------------------------------- class AnyDataNameItem ( Template ): """ An abstract base class implementation of the ITemplateDataNameItem interface that looks for all specified values in its input context or optionally any of its sub-contexts and outputs a context containing all such values found. """ implements ( ITemplateDataNameItem ) #-- 'ITemplateDataNameItem' Interface Implementation ----------------------- # The data context which this data name item should match against: input_data_context = Instance( ITemplateDataContext ) # The data context containing the data values and/or contexts this data # name item matches: output_data_context = Instance( ITemplateDataContext ) # The ITemplateChoice instance representing the current settings of the # data name item. This value must be read/write, and must be overridden by # sublasses. data_name_item_choice = Property # The alternative choices the user has for the data name item settings for # the current input data context. The list may be empty, in which case the # user cannot change the settings of the data name item. This value can be # read only, and must be overridden by subclasses. data_name_item_choices = Property #-- Public Traits ---------------------------------------------------------- # Should all sub-contexts be included in the search: recursive = TBool( False ) # Should included sub-contexts be flattened into a single context? flatten = TBool( False ) #-- Private Traits --------------------------------------------------------- # The current recursive setting: current_recursive = TBool( False ) # The current input data context: current_input_data_context = Property #-- Abstract Methods (Must be overridden in subclasses) -------------------- def filter ( self, name, value ): """ Returns **True** if the specified context data *name* and *value* should be included in the output context; and **False** otherwise. """ raise NotImplementedError #-- Property Implementations ----------------------------------------------- def _get_data_name_item_choice ( self ): raise NotImplementedError def _set_data_name_item_choice ( self, value ): raise NotImplementedError def _get_data_name_item_choices ( self ): raise NotImplementedError def _get_current_input_data_context ( self ): return self.input_data_context #-- Trait Event Handlers --------------------------------------------------- def _recursive_changed ( self, value ): """ Handles the primary recursive setting being changed. """ self.current_recursive = value def _input_data_context_changed ( self ): """ Handles the 'input_data_context' trait being changed. """ self.inputs_changed() #-- Private Methods -------------------------------------------------------- def inputs_changed ( self ): """ Handles any input value being changed. This method should be called by subclasses when any of their input values change. """ output_context = None input_context = self.current_input_data_context if input_context is not None: values = {} if self.current_recursive: if self.flatten: self._add_context( input_context, values ) else: self._copy_context( input_context, values ) else: self._add_values( input_context, values, '' ) if len( values ) > 0: output_context = TemplateDataContext( data_context_path = input_context.data_context_path, data_context_name = input_context.data_context_name, values = values ) self.output_data_context = output_context def _add_values ( self, input_context, values, path = '' ): """ Adds all of the matching values in the specified *input_context* to the specified *values* dictionary. """ # Filter each name/value in the current input context to see if it # should be added to the output values: filter = self.filter gdcv = input_context.get_data_context_value for name in input_context.data_context_values: value = gdcv( name ) if self.filter( name, value ): values[ path_for( path, name ) ] = value def _add_context ( self, input_context, values, path = '' ): """ Adds all of the matching values in the specified *input_context* to the specified *output_context*, and then applies itself recursively to all contexts contained in the specified *input_context*. """ # Add all of the filtered values in the specified input context: self._add_values( input_context, values, path ) # Now process all of the input context's sub-contexts: gdc = input_context.get_data_context for name in input_context.data_contexts: self._add_context( gdc( name ), values, path_for( path, input_context.data_context_name ) ) def _copy_context ( self, input_context ): """ Clone the input context so that the result only contains values and contexts which contain valid values and are not empty. """ values = {} contexts = {} # Add all of the filtered values in the specified input context: self._add_values( input_context, values ) # Now process all of the input context's sub-contexts: gdc = input_context.get_data_context for name in input_context.data_contexts: context = self._copy_context( gdc( name ) ) if context is not None: contexts[ name ] = context if (len( values ) == 0) and (len( contexts ) == 0): return None return TemplateDataContext( data_context_path = input_context.data_context_path, data_context_name = input_context.data_context_name, values = values, contexts = contexts ) apptools-4.1.0/apptools/template/template_data_names.py0000644000175100001440000002076511674464005024374 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Defines the TemplateDataNames class used to manage application data source # bindings to named context data. # # Written by: David C. Morrill # # Date: 07/26/2007 # # (c) Copyright 2007 by Enthought, Inc. # #------------------------------------------------------------------------------- """ Defines the TemplateDataNames class used to manage application data source bindings to named context data. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- from traits.api \ import HasPrivateTraits, Instance, Int, List, Property, Delegate, \ cached_property from traitsui.api \ import View, Item, TableEditor, EnumEditor, TextEditor from traitsui.table_column \ import ObjectColumn from template_data_name \ import TemplateDataName from itemplate_data_context \ import ITemplateDataContext from template_choice \ import TemplateChoice #------------------------------------------------------------------------------- # Table editor support for editing a list of TemplateDataName objects: #------------------------------------------------------------------------------- class BindingsColumn ( ObjectColumn ): # The column index: index = Int #-- ObjectColumn Method Overrides ------------------------------------------ def is_editable ( self, object ): return (self.index < len( object.data_name.items )) def get_editor ( self, object ): return EnumEditor( values = [ dnic.choice_value for dnic in object.data_name.items[ self.index ].data_name_item_choices ] + [ self.get_raw_value( object ) ] ) class ResolvedColumn ( ObjectColumn ): def get_raw_value( self, object ): if object.data_name.resolved: return '' return 'X' def get_cell_color ( self, object ): if object.data_name.resolved: return self.read_only_cell_color_ return self.cell_color_ class OptionalColumn ( ObjectColumn ): def get_raw_value( self, object ): return ' X'[ object.data_name.optional ] # Define the table editor: table_editor = TableEditor( columns_name = 'table_columns', configurable = False, auto_size = False, sortable = False, scroll_dy = 4, selection_bg_color = None ) # The standard columns: std_columns = [ ResolvedColumn( name = 'resolved', label = '?', editable = False, width = 20, horizontal_alignment = 'center', cell_color = 0xFF8080 ), OptionalColumn( name = 'optional', label = '*', editable = False, width = 20, horizontal_alignment = 'center' ), ObjectColumn( name = 'description', editor = TextEditor(), width = 0.47 ) ] #------------------------------------------------------------------------------- # 'TemplateDataNames' class: #------------------------------------------------------------------------------- class TemplateDataNames ( HasPrivateTraits ): #-- Public Traits ---------------------------------------------------------- # The data context to which bindings are made: context = Instance( ITemplateDataContext ) # The current set of data names to be bound to the context: data_names = List( TemplateDataName ) # The list of unresolved, required bindings: unresolved_data_names = Property( depends_on = 'data_names.resolved' ) # The list of optional bindings: optional_data_names = Property( depends_on = 'data_names.optional' ) # The list of unresolved optional bindings: unresolved_optional_data_names = Property( depends_on = 'data_names.[resolved,optional]' ) #-- Private Traits --------------------------------------------------------- # List of 'virtual' data names for use by table editor: virtual_data_names = List # The list of table editor columns: table_columns = Property( depends_on = 'data_names' ) # List( ObjectColumn ) #-- Traits View Definitions ------------------------------------------------ view = View( Item( 'virtual_data_names', show_label = False, style = 'custom', editor = table_editor ) ) #-- Property Implementations ----------------------------------------------- @cached_property def _get_unresolved_data_names ( self ): return [ dn for dn in self.data_names if (not dn.resolved) and (not dn.optional) ] @cached_property def _get_optional_data_names ( self ): return [ dn for dn in self.data_names if dn.optional ] @cached_property def _get_unresolved_optional_data_names ( self ): return [ dn for dn in self.data_names if (not dn.resolved) and dn.optional ] @cached_property def _get_table_columns ( self ): n = max( [ len( dn.items ) for dn in self.data_names ] ) if n == 1: return std_columns + [ BindingsColumn( name = 'value0', label = 'Name', width = 0.43 ) ] width = 0.43 / n return (std_columns + [ BindingsColumn( name = 'value%d' % i, index = i, label = 'Name %d' % ( i + 1 ), width = width ) for i in range( n ) ]) #-- Trait Event Handlers --------------------------------------------------- def _context_changed ( self, context ): for data_name in self.data_names: data_name.context = context def _data_names_changed ( self, old, new ): """ Handles the list of 'data_names' being changed. """ # Make sure that all of the names are unique: new = set( new ) # Update the old and new context links: self._update_contexts( old, new ) # Update the list of virtual names based on the new set: dns = [ VirtualDataName( data_name = dn ) for dn in new ] dns.sort( lambda l, r: cmp( l.description, r.description ) ) self.virtual_data_names = dns def _data_names_items_changed ( self, event ): # Update the old and new context links: old, new = event.old, event.new self._update_contexts( old, new ) # Update the list of virtual names based on the old and new sets: i = event.index self.virtual_data_names[ i: i + len( old ) ] = [ VirtualDataName( data_name = dn ) for dn in new ] #-- Private Methods -------------------------------------------------------- def _update_contexts ( self, old, new ): """ Updates the data context for an old and new set of data names. """ for data_name in old: data_name.context = None context = self.context for data_name in new: data_name.context = context #------------------------------------------------------------------------------- # 'VirtualDataName' class: #------------------------------------------------------------------------------- # Define the 'VirtualValue' property: def _get_virtual_data ( self, name ): return self.data_name.items[ self.trait( name ).index ].data_name_item_choice.choice_value def _set_virtual_data ( self, name, new_value ): old_value = _get_virtual_data( self, name ) if old_value != new_value: self.data_name.items[ self.trait( name ).index ].data_name_item_choice = \ TemplateChoice( choice_value = new_value ) self.trait_property_changed( name, old_value, new_value ) VirtualValue = Property( _get_virtual_data, _set_virtual_data ) class VirtualDataName ( HasPrivateTraits ): # The TemplateDataName this is a virtual copy of: data_name = Instance( TemplateDataName ) # The data name description: description = Delegate( 'data_name', modify = True ) # The 'virtual' traits of this object: value0 = VirtualValue( index = 0 ) value1 = VirtualValue( index = 1 ) value2 = VirtualValue( index = 2 ) value3 = VirtualValue( index = 3 ) value4 = VirtualValue( index = 4 ) value5 = VirtualValue( index = 5 ) apptools-4.1.0/apptools/template/api.py0000644000175100001440000000214411674464005021145 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Defines the external API for the template package. # # Written by: David C. Morrill # # Date: 07/26/2007 # # (c) Copyright 2007 by Enthought, Inc. # #------------------------------------------------------------------------------- """ Defines the external API for the template package. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- from template_traits \ import TDataSource, TInstance, TList, TInt, TFloat, TStr, TBool, TRange, \ TEnum, TDerived from itemplate \ import ITemplate from imutable_template \ import IMutableTemplate from itemplate_data_context \ import ITemplateDataContext, ITemplateDataContextError from template_data_name \ import TemplateDataName from template_data_names \ import TemplateDataNames from template_impl \ import Template from mutable_template \ import MutableTemplate from template_choice \ import TemplateChoice apptools-4.1.0/apptools/template/template_data_name.py0000644000175100001440000001045411674464005024203 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Defines the TemplateDataName class used for binding a data source to a data # context. # # Written by: David C. Morrill # # Date: 07/27/2007 # # (c) Copyright 2007 by Enthought, Inc. # #------------------------------------------------------------------------------- """ Defines the TemplateDataName class used for binding a data source to a data context. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- from traits.api \ import HasPrivateTraits, Instance, Bool, Property, Undefined, \ on_trait_change, cached_property from itemplate_data_name_item \ import ITemplateDataNameItem from itemplate_data_context \ import ITemplateDataContext from template_impl \ import Template from template_traits \ import TList, TStr, TBool #------------------------------------------------------------------------------- # 'TemplateDataName' interface: #------------------------------------------------------------------------------- class TemplateDataName ( Template ): """ Defines the TemplateDataName class used for binding a data source to a data context. """ #-- Public Template Traits ------------------------------------------------- # The list of ITemplateDataNameItem's used to match the data context: items = TList( ITemplateDataNameItem ) # A description of the template data name (for use in a user interface): description = TStr # Is this binding optional? optional = TBool( False ) #-- Public Non-Template Traits --------------------------------------------- # The data context the template name is matching against currently: context = Instance( ITemplateDataContext ) # Is the binding resolved? resolved = Property( Bool, depends_on = 'items.output_data_context' ) # The actual name of the context data that the data source is bound to: context_data_name = Property # The context data the data source is bound to: context_data = Property #-- Property Implementations ----------------------------------------------- @cached_property def _get_resolved ( self ): if len( self.items ) == 0: return False context = self.items[-1].output_data_context return ((context is not None) and (len( context.data_context_values ) == 1) and (len( context.data_contexts ) == 0)) def _get_context_data_name ( self ): if self.resolved: context = self.items[-1].output_data_context path = context.data_context_path if path != '': path += '.' name = context.data_context_name if name != '': name += '.' return '%s%s%s' % ( path, name, context.data_context_values[0] ) return '' def _get_context_data ( self ): if self.resolved: context = self.items[-1].output_data_context return context.get_data_context_value( context.data_context_values[0] ) return Undefined #-- Trait Event Handlers --------------------------------------------------- def _context_changed ( self, context ): """ Resets the name whenever the context is changed. """ self._reset() @on_trait_change( ' items' ) def _on_items_changed ( self ): """ Resets the name whenever any of the name items are changed. """ self._reset() @on_trait_change( 'items:output_data_context' ) def _on_output_data_context_changed ( self, item, name, old, new ): i = self.items.index( item ) if i < (len( self.items ) - 1): self.items[ i + 1 ].input_data_context = new #-- Private Methods -------------------------------------------------------- def _reset ( self ): """ Resets the name whenever any significant change occurs. """ items = self.items n = len( items ) if n > 0: items[0].input_data_context = self.context for i in range( 0, n - 1 ): items[ i + 1 ].input_data_context = \ items[ i ].output_data_context apptools-4.1.0/apptools/template/itemplate_data_source.py0000644000175100001440000000267111674464005024736 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Defines the ITemplateDataSource interface for creating 'live' application # data sources from a templatized data source object. # # Written by: David C. Morrill # # Date: 07/26/2007 # # (c) Copyright 2007 by Enthought, Inc. # #------------------------------------------------------------------------------- """ Defines the ITemplateDataSource interface for creating 'live' application data sources from a templatized data source object. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- from traits.api \ import Interface #------------------------------------------------------------------------------- # 'ITemplateDataSource' interface: #------------------------------------------------------------------------------- class ITemplateDataSource ( Interface ): """ Defines the ITemplateDataSource interface for creating 'live' application data sources from a templatized data source object. """ def name_from_data_source ( self ): """ Allows the object to provide a description of the possibly optional data binding it requires. Returns ------- A **TemplateDataName** object describing the binding the data source object requires. """ apptools-4.1.0/apptools/template/itemplate_data_context.py0000644000175100001440000000646711674464005025131 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Defines the ITemplateDataContext interface for accessing a named collection # of data that can be bound to a templatized object when converting it to a # 'live' set of objects. # # Written by: David C. Morrill # # Date: 07/26/2007 # # (c) Copyright 2007 by Enthought, Inc. # #------------------------------------------------------------------------------- """ Defines the ITemplateDataContext interface for accessing a named collection of data that can be bound to a templatized object when converting it to a 'live' set of objects. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- from traits.api\ import Interface, List, Str #------------------------------------------------------------------------------- # 'ITemplateDataContext' interface: #------------------------------------------------------------------------------- class ITemplateDataContext ( Interface ): """ Defines the ITemplateDataContext interface for accessing a named collection of data that can be bound to a templatized object when converting it to a 'live' set of objects. """ # The path to this data context (does not include the 'data_context_name'): data_context_path = Str # The name of the data context: data_context_name = Str # A list of the names of the data values in this context: data_context_values = List( Str ) # The list of the names of the sub-contexts of this context: data_contexts = List( Str ) def get_data_context_value ( self, name ): """ Returns the data value with the specified *name*. Raises a **ITemplateDataContextError** if *name* is not defined as a data value in the context. Parameters ---------- name : A string specifying the name of the context data value to be returned. Returns ------- The data value associated with *name* in the context. The type of the data is application dependent. Raises **ITemplateDataContextError** if *name* is not associated with a data value in the context. """ def get_data_context ( self, name ): """ Returns the **ITemplateDataContext** value associated with the specified *name*. Raises **ITemplateDataContextError** if *name* is not defined as a data context in the context. Parameters ---------- name : A string specifying the name of the data context to be returned. Returns ------- The **ITemplateDataContext** associated with *name* in the context. Raises **ITemplateDataContextError** if *name* is not associated with a data context in the context. """ #------------------------------------------------------------------------------- # 'ITemplateDataContextError' exception: #------------------------------------------------------------------------------- class ITemplateDataContextError ( Exception ): """ The exception class associated with the **ITemplateDataContext** interface. """ apptools-4.1.0/apptools/template/template_impl.py0000644000175100001440000001410311674464005023226 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Defines the Template class, which provides a default implementation of the # ITemplate interface that can be used when defining templatizable objects. # # Written by: David C. Morrill # # Date: 07/26/2007 # # (c) Copyright 2007 by Enthought, Inc. # #------------------------------------------------------------------------------- """ Defines the Template class, which provides a default implementation of the ITemplate interface that can be used when defining templatizable objects. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- from traits.api \ import HasPrivateTraits, Undefined, implements from itemplate \ import ITemplate #------------------------------------------------------------------------------- # Metadata filter for extracting 'object' and 'datasource' items: #------------------------------------------------------------------------------- def instance_or_datasource ( value ): return value in ( 'instance', 'datasource' ) #------------------------------------------------------------------------------- # Metadata filter for extracting traits with no 'template' metadata: #------------------------------------------------------------------------------- def is_none ( value ): return (value is None) #------------------------------------------------------------------------------- # 'Template' class: #------------------------------------------------------------------------------- class Template ( HasPrivateTraits ): """ Defines the Template class, which provides a default implementation of the ITemplate interface that can be used when defining templatizable objects. """ implements( ITemplate ) #-- 'ITemplate' Interface Implementation ----------------------------------- def names_from_template ( self ): """ Returns a list of **TemplateDataName** objects, one for each bindable data source contained within the template. Each **TemplateDataName** object supports unresolved bindings in addition to resolved bindings. Also, a **TemplateDataName** may be marked as *optional*, meaning the if the name remains unresolved, the application can take an alternative action (e.g. omit the data from a plot, or substitute default data, etc.). Returns ------- A list of **TemplateDataName** objects, one for each bindable data source contained in the template. """ data_names = [] # Recursively propagate the request to all contained templatizable # objects: for value in self.get( template = 'instance' ).values(): if isinstance( value, list ): for v in value: data_names.extend( v.names_from_template() ) elif value is not None: data_names.extend( value.names_from_template() ) # Add the bindings for all of our local data source objects: for value in self.get( template = 'datasource' ).values(): if isinstance( value, list ): for v in value: data_names.append( v.name_from_data_source() ) elif value is not None: data_names.append( value.name_from_data_source() ) # Return the list of TemplateDataName objects we collected: return data_names def object_from_template ( self ): """ Activates the object from its template data. Returns ------- The original object. """ # Convert all our data source objects to 'live' objects: for value in self.get( template = 'datasource' ).values(): if isinstance( value, list ): for v in value: v.object_from_template() elif value is not None: value.object_from_template() # Recursively propagate the request to all contained templatizable # objects: for value in self.get( template = 'instance' ).values(): if isinstance( value, list ): for v in value: v.object_from_template() elif value is not None: value.object_from_template() # Reset all of our 'live' objects to undefined: for name in self.trait_names( template = 'derived' ): setattr( self, name, Undefined ) # Return ourselves as the new, live version of the object: return self def template_from_object ( self ): """ Returns a *templatized* version of the object that is safe for serialization. Returns ------- A new object of the same class as the original. """ # Mark all traits without 'template' metadata as 'transient': for trait in self.traits( template = is_none ).values(): trait.transient = True # Collect all of the data source and templatizable objects we contain: contents = self.get( template = instance_or_datasource ) # Convert them all to new objects in template form: for name, value in contents.items(): if isinstance( value, list ): contents[ name ] = [ v.template_from_object() for v in value ] else: contents[ name ] = value.template_from_object() # Add all of the simple values which can just be copied: contents.update( self.get( template = 'copy' ) ) # Finally return a new instance of our class containing just the # templatizable values: return self.__class__( **contents ) def activate_template ( self ): """ Converts all contained 'TDerived' objects to real objects using the template traits of the object. This method must be overridden in subclasses. Returns ------- None """ raise NotImplementedError apptools-4.1.0/apptools/template/itemplate_choice.py0000644000175100001440000000206011674464005023667 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Defines the ITemplateChoice interface used by ITemplateDataNameItem # interface. # # Written by: David C. Morrill # # Date: 07/29/2007 # # (c) Copyright 2007 by Enthought, Inc. # #------------------------------------------------------------------------------- """ Defines the ITemplateChoice interface used by ITemplateDataNameItem interface. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- from traits.api \ import Interface, Str #------------------------------------------------------------------------------- # 'ITemplateChoice' interface: #------------------------------------------------------------------------------- class ITemplateChoice ( Interface ): """ Defines the ITemplateChoice interface used by ITemplateDataNameItem interface. """ # The user interface string for this choice: choice_value = Str apptools-4.1.0/apptools/template/itemplate_data_name_item.py0000644000175100001440000000512211674464005025366 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Defines the ITemplateDataNameItem interface used by elements of a # TemplateDataName. # # Written by: David C. Morrill # # Date: 07/27/2007 # # (c) Copyright 2007 by Enthought, Inc. # #------------------------------------------------------------------------------- """ Defines the ITemplateDataNameItem interface used by elements of a TemplateDataName. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- from traits.api \ import Interface, Instance, List from itemplate_data_context \ import ITemplateDataContext from itemplate_choice \ import ITemplateChoice #------------------------------------------------------------------------------- # 'ITemplateDataNameItem' interface: #------------------------------------------------------------------------------- class ITemplateDataNameItem ( Interface ): """ Defines the ITemplateDataNameItem interface used by elements of a TemplateDataName. The contents of the *input_data_context* and *output_data_context* are assumed to be immutable. That is, no changes should occur to the data contexts once they have been assigned to the **ITemplateDataNameItem** traits. However, new input contexts can be assigned, in which case a new output context should be created and assigned to the output context (once it has been fully populated). Also, the *output_data_context* should be **None** if the *input_data_context* value is **None** or the object cannot match any values in the *input_data_context*. """ # The data context which this data name item should match against. This # value must be read/write. input_data_context = Instance( ITemplateDataContext ) # The data context containing the data values and/or contexts this data # name item matches. This value must be read/write. output_data_context = Instance( ITemplateDataContext ) # The ITemplateChoice instance representing the current settings of the # data name item. This value must be read/write. data_name_item_choice = Instance( ITemplateChoice ) # The alternative choices the user has for the data name item settings for # the current input data context. The list may be empty, in which case the # user cannot change the settings of the data name item. This value can be # read only. data_name_item_choices = List( ITemplateChoice ) apptools-4.1.0/apptools/template/itemplate.py0000644000175100001440000000451711674464005022366 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Defines the ITemplate interface used to create templatizable object # hierarchies. # # Written by: David C. Morrill # # Date: 07/26/2007 # # (c) Copyright 2007 by Enthought, Inc. # #------------------------------------------------------------------------------- """ Defines the ITemplate interface used to create templatizable object hierarchies. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- from traits.api \ import Interface #------------------------------------------------------------------------------- # 'ITemplate' interface: #------------------------------------------------------------------------------- class ITemplate ( Interface ): """ Defines the ITemplate interface used to create templatizable object hierarchies. """ def names_from_template ( self ): """ Returns a list of **TemplateDataName** objects, one for each bindable data source contained within the template. Each **TemplateDataName** object supports unresolved bindings in addition to resolved bindings. Also, a **TemplateDataName** may be marked as *optional*, meaning the if the name remains unresolved, the application can take an alternative action (e.g. omit the data from a plot, or substitute default data, etc.). Returns ------- A list of **TemplateDataName** objects, one for each bindable data source contained in the template. """ def object_from_template ( self ): """ Activates the object from its template data. Returns ------- The original object. """ def template_from_object ( self ): """ Returns a *templatized* version of the object that is safe for serialization. Returns ------- A new object of the same class as the original. """ def activate_template ( self ): """ Converts all contained 'TDerived' objects to real objects using the template traits of the object. Returns ------- None """ apptools-4.1.0/apptools/template/mutable_template.py0000644000175100001440000000250111674464005023715 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # A concrete base class that implements the IMutableTemplate interface. # # Written by: David C. Morrill # # Date: 08/01/2007 # # (c) Copyright 2007 by Enthought, Inc. # #------------------------------------------------------------------------------- """ A concrete base class that implements the IMutableTemplate interface. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- from traits.api \ import Event, implements from template_impl \ import Template from imutable_template \ import IMutableTemplate #------------------------------------------------------------------------------- # 'MutableTemplate' class: #------------------------------------------------------------------------------- class MutableTemplate ( Template ): """ A concrete base class that implements the IMutableTemplate interface. """ implements( IMutableTemplate ) #-- IMutableTemplate Interface Implementation ------------------------------ # An event fired when the template mutates (i.e. changes in some way that # may affect the number of data sources it exposes, and so on): template_mutated = Event apptools-4.1.0/apptools/template/imutable_template.py0000644000175100001440000000242511674464005024073 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Defines the IMutableTemplate interface used to define modifiable templates. # # Written by: David C. Morrill # # Date: 08/01/2007 # # (c) Copyright 2007 by Enthought, Inc. # #------------------------------------------------------------------------------- """ Defines the IMutableTemplate interface used to define modifiable templates. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- from traits.api \ import Event from itemplate \ import ITemplate #------------------------------------------------------------------------------- # 'IMutableTemplate' interface: #------------------------------------------------------------------------------- class IMutableTemplate ( ITemplate ): """ Defines the IMutableTemplate interface used to define modifiable templates. Extends the ITemplate interface by supporting the ability for the template to modify itself. """ # An event fired when the template mutates (i.e. changes in some way that # may affect the number of data sources it exposes, and so on): template_mutated = Event apptools-4.1.0/apptools/template/template_choice.py0000644000175100001440000000202111674464005023513 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # A concrete implementation of the ITemplateChoice interface that can be used # as is for simple cases, or extended for more complex ones. # # Written by: David C. Morrill # # Date: 07/29/2007 # # (c) Copyright 2007 by Enthought, Inc. # #------------------------------------------------------------------------------- #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- from traits.api \ import HasPrivateTraits, Str, implements from itemplate_choice \ import ITemplateChoice #------------------------------------------------------------------------------- # 'TemplateChoice' class: #------------------------------------------------------------------------------- class TemplateChoice ( HasPrivateTraits ): implements ( ITemplateChoice ) # The user interface string for this choice: choice_value = Str apptools-4.1.0/apptools/template/__init__.py0000644000175100001440000000053111674464005022131 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007 by Enthought, Inc. # All rights reserved. #------------------------------------------------------------------------------ """ Supports creating templatizable object hierarchies. Part of the AppTools project of the Enthought Tool Suite. """ apptools-4.1.0/apptools/template/template_traits.py0000644000175100001440000000555311674464005023604 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Trait definitions useful when creating templatizable classes. # # Written by: David C. Morrill # # Date: 07/26/2007 # # (c) Copyright 2007 by Enthought, Inc. # #------------------------------------------------------------------------------- """ Trait definitions useful when creating templatizable classes. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- from traits.api \ import Instance, Int, Float, Str, List, Bool, Range, TraitType, Undefined from itemplate_data_source \ import ITemplateDataSource #------------------------------------------------------------------------------- # Trait definitions: #------------------------------------------------------------------------------- # Data source template trait: TDataSource = Instance( ITemplateDataSource, template = 'datasource' ) # Templatizable object trait: def TInstance ( *args, **kw ): kw[ 'template' ] = 'instance' return Instance( *args, **kw ) # A list of templatizable object traits: def TList( *args, **kw ): kw[ 'template' ] = 'instance' return List( *args, **kw ) # Simple, copyable template traits: TInt = Int( template = 'copy' ) TFloat = Float( template = 'copy' ) TStr = Str( template = 'copy' ) TBool = Bool( template = 'copy' ) def TRange ( *args, **kw ): kw[ 'template' ] = 'copy' return Range( *args, **kw ) def TEnum ( *args, **kw ): kw[ 'template' ] = 'copy' return Enum( *args, **kw ) #------------------------------------------------------------------------------- # 'TDerived' trait: #------------------------------------------------------------------------------- class TDerived ( TraitType ): """ Defines a trait property for handling attributes whose value depends upon the templatizable traits of an object. Note that the implementation of the *activate_template* method is not allowed to read the value of any **TDerived** traits, it may only set them. """ # Base trait metadata: metadata = { 'template': 'derived', 'transient': True } def get ( self, object, name ): name += '_' value = object.__dict__.get( name, Undefined ) if value is not Undefined: return value object.activate_template() value = object.__dict__.get( name, Undefined ) if value is not Undefined: return value object.__dict__[ name ] = None return None def set ( self, object, name, value ): xname = name + '_' old = object.__dict__.get( xname, Undefined ) if old is not value: object.__dict__[ xname ] = value object.trait_property_changed( name, old, value ) apptools-4.1.0/apptools/template/test/0000755000175100001440000000000011674464005021000 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/template/test/scatter_plot_nm.py0000644000175100001440000001637111674464005024557 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # A n row x m column scatter-plot template defined as a test for the template # package. # # Written by: David C. Morrill # (based on the original cp.plot geo_scatter_plot.py file) # # Date: 08/01/2007 # # (c) Copy 2007 by Enthought, Inc. # #------------------------------------------------------------------------------- """ A n row x m column scatter-plot template defined as a test for the template package. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- from traits.api \ import Undefined from traitsui.api \ import View, VGroup, Item, Label, Theme, TextEditor from traitsui.wx.themed_slider_editor \ import ThemedSliderEditor from traitsui.wx.themed_text_editor \ import ThemedTextEditor from enable.api \ import ColorTrait from chaco.api \ import GridPlotContainer, PlotComponent from chaco.scatter_markers \ import marker_trait from apptools.template.api \ import MutableTemplate, TRange, TStr, TList, TDerived from enable_editor \ import EnableEditor from scatter_plot \ import ScatterPlot #------------------------------------------------------------------------------- # Trait definitions: #------------------------------------------------------------------------------- # Template color trait: TColor = ColorTrait( template = 'copy' ) #------------------------------------------------------------------------------- # 'ScatterPlotNM' class: #------------------------------------------------------------------------------- class ScatterPlotNM ( MutableTemplate ): #-- Template Traits -------------------------------------------------------- # The title of the plot: title = TStr( 'NxM Scatter Plots' ) # The type of marker to use. This is a mapped trait using strings as the # keys: marker = marker_trait( template = 'copy', event = 'update' ) # The pixel size of the marker (doesn't include the thickness of the # outline): marker_size = TRange( 1, 5, 1, event = 'update' ) # The thickness, in pixels, of the outline to draw around the marker. If # this is 0, no outline will be drawn. line_width = TRange( 0.0, 5.0, 1.0 ) # The fill color of the marker: color = TColor( 'red', event = 'update' ) # The color of the outline to draw around the marker outline_color = TColor( 'black', event = 'update' ) # The number of rows of plots: rows = TRange( 1, 3, 1, event = 'grid' ) # The number of columns of plots: columns = TRange( 1, 5, 1, event = 'grid' ) # The contained scatter plots: scatter_plots = TList( ScatterPlot ) #-- Derived Traits --------------------------------------------------------- plot = TDerived #-- Traits UI Views -------------------------------------------------------- # The scatter plot view: template_view = View( VGroup( Item( 'title', show_label = False, style = 'readonly', editor = ThemedTextEditor( theme = Theme( '@GBB', alignment = 'center' ) ) ), Item( 'plot', show_label = False, resizable = True, editor = EnableEditor(), item_theme = Theme( '@GF5', margins = 0 ) ) ), resizable = True ) # The scatter plot options view: options_view = View( VGroup( VGroup( Label( 'Scatter Plot Options', item_theme = Theme( '@GBB', alignment = 'center' ) ), show_labels = False ), VGroup( Item( 'title', editor = TextEditor() ), Item( 'marker' ), Item( 'marker_size', editor = ThemedSliderEditor() ), Item( 'line_width', label = 'Line Width', editor = ThemedSliderEditor() ), Item( 'color', label = 'Fill Color' ), Item( 'outline_color', label = 'Outline Color' ), Item( 'rows', editor = ThemedSliderEditor() ), Item( 'columns', editor = ThemedSliderEditor() ), group_theme = Theme( '@GF5', margins = ( -5, -1 ) ), item_theme = Theme( '@G0B', margins = 0 ) ) ) ) #-- ITemplate Interface Implementation ------------------------------------- def activate_template ( self ): """ Converts all contained 'TDerived' objects to real objects using the template traits of the object. This method must be overridden in subclasses. Returns ------- None """ plots = [] i = 0 for r in range( self.rows ): row = [] for c in range( self.columns ): plot = self.scatter_plots[i].plot if plot is None: plot = PlotComponent() row.append( plot ) i += 1 plots.append( row ) self.plot = GridPlotContainer( shape = ( self.rows, self.columns ) ) self.plot.component_grid = plots #-- Default Values --------------------------------------------------------- def _scatter_plots_default ( self ): """ Returns the default value for the scatter plots list. """ plots = [] for i in range( self.rows * self.columns ): plots.append( ScatterPlot() ) self._update_plots( plots ) return plots #-- Trait Event Handlers --------------------------------------------------- def _update_changed ( self, name, old, new ): """ Handles a plot option being changed. """ for sp in self.scatter_plots: setattr( sp, name, new ) self.plot = Undefined def _grid_changed ( self ): """ Handles the grid size being changed. """ n = self.rows * self.columns plots = self.scatter_plots if n < len( plots ): self.scatter_plots = plots[:n] else: for j in range( len( plots ), n ): plots.append( ScatterPlot() ) self._update_plots( plots ) self.template_mutated = True #-- Private Methods -------------------------------------------------------- def _update_plots ( self, plots ): """ Update the data sources for all of the current plots. """ index = None i = 0 for r in range( self.rows ): for c in range( self.columns ): sp = plots[i] i += 1 desc = sp.value.description col = desc.rfind( '[' ) if col >= 0: desc = desc[:col] sp.value.description = '%s[%d,%d]' % ( desc, r, c ) sp.value.optional = True if index is None: index = sp.index index.description = 'Shared Plot Index' index.optional = True else: sp.index = index apptools-4.1.0/apptools/template/test/enable_editor.py0000644000175100001440000000504711674464005024154 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Traits UI editor for displaying Enable Components. # # Written by: David Morrill # # Date: 07/30/2007 # # (c) Copyright 2007 by Enthought, Inc. # #------------------------------------------------------------------------------- """ Traits UI editor for displaying Enable Components. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- import wx from traitsui.wx.editor \ import Editor from traitsui.wx.basic_editor_factory \ import BasicEditorFactory from enable.wx_backend.api \ import Window #------------------------------------------------------------------------------- # '_EnableEditor' class: #------------------------------------------------------------------------------- class _EnableEditor ( Editor ): """ Traits UI editor for displaying Enable Components. """ # Override the default value to allow the control to be resizable: scrollable = True #--------------------------------------------------------------------------- # Finishes initializing the editor by creating the underlying toolkit # widget: #--------------------------------------------------------------------------- def init ( self, parent ): """ Finishes initializing the editor by creating the underlying toolkit widget. """ self._window = Window( parent, -1, component = self.value, bg_color = ( 0.698, 0.698, 0.698, 1.0 ) ) self.control = self._window.control self.control.SetSize( wx.Size( 300, 300 ) ) self.set_tooltip() #--------------------------------------------------------------------------- # Updates the editor when the object trait changes external to the editor: #--------------------------------------------------------------------------- def update_editor ( self ): """ Updates the editor when the object trait changes externally to the editor. """ self._window.component = self.value #------------------------------------------------------------------------------- # Create the editor factory object: #------------------------------------------------------------------------------- # wxPython editor factory for Enable component editors: class EnableEditor ( BasicEditorFactory ): # The editor class to be created: klass = _EnableEditor apptools-4.1.0/apptools/template/test/scatter_plot.py0000644000175100001440000001576411674464005024072 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # A simple scatter-plot template defined as a test for the template package. # # Written by: David C. Morrill # (based on the original cp.plot geo_scatter_plot.py file) # # Date: 07/30/2007 # # (c) Copy 2007 by Enthought, Inc. # #------------------------------------------------------------------------------- """ A simple scatter-plot template defined as a test for the template package. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- from traits.api \ import HasPrivateTraits, Undefined from traitsui.api \ import View, VGroup, Item, Label, Theme, TextEditor from traitsui.wx.themed_slider_editor \ import ThemedSliderEditor from traitsui.wx.themed_text_editor \ import ThemedTextEditor from chaco.api \ import ScatterPlot, ArrayPlotData, Plot from chaco.tools.api \ import PanTool, SimpleZoom from enable.api \ import ColorTrait from chaco.scatter_markers \ import marker_trait from apptools.template.api \ import Template, TRange, TStr, TDerived, TDataSource from apptools.template.impl.api \ import TemplateDataSource, ValueDataNameItem from enable_editor \ import EnableEditor #------------------------------------------------------------------------------- # Trait definitions: #------------------------------------------------------------------------------- # Template color trait: TColor = ColorTrait( template = 'copy' ) #------------------------------------------------------------------------------- # 'ScatterPlot' class: #------------------------------------------------------------------------------- class ScatterPlot ( Template ): #-- Template Traits -------------------------------------------------------- # The plot index data source: index = TDataSource # The plot value data source: value = TDataSource # The title of the plot: title = TStr( 'Scatter Plot' ) # The type of marker to use. This is a mapped trait using strings as the # keys: marker = marker_trait( template = 'copy', event = 'update' ) # The pixel size of the marker (doesn't include the thickness of the # outline): marker_size = TRange( 1, 5, 1, event = 'update' ) # The thickness, in pixels, of the outline to draw around the marker. If # this is 0, no outline will be drawn. line_width = TRange( 0.0, 5.0, 1.0 ) # The fill color of the marker: color = TColor( 'red', event = 'update' ) # The color of the outline to draw around the marker outline_color = TColor( 'black', event = 'update' ) #-- Derived Traits --------------------------------------------------------- plot = TDerived # Instance( ScatterPlot ) #-- Traits UI Views -------------------------------------------------------- # The scatter plot view: template_view = View( VGroup( Item( 'title', show_label = False, style = 'readonly', editor = ThemedTextEditor( theme = Theme( '@GBB', alignment = 'center' ) ) ), Item( 'plot', show_label = False, resizable = True, editor = EnableEditor(), item_theme = Theme( '@GF5', margins = 0 ) ) ), resizable = True ) # The scatter plot options view: options_view = View( VGroup( VGroup( Label( 'Scatter Plot Options', item_theme = Theme( '@GBB', alignment = 'center' ) ), show_labels = False ), VGroup( Item( 'title', editor = TextEditor() ), Item( 'marker' ), Item( 'marker_size', editor = ThemedSliderEditor() ), Item( 'line_width', label = 'Line Width', editor = ThemedSliderEditor() ), Item( 'color', label = 'Fill Color' ), Item( 'outline_color', label = 'Outline Color' ), group_theme = Theme( '@GF5', margins = ( -5, -1 ) ), item_theme = Theme( '@G0B', margins = 0 ) ) ) ) #-- Default Values --------------------------------------------------------- def _index_default ( self ): """ Returns the default value for the 'index' trait. """ return TemplateDataSource( items = [ ValueDataNameItem( name = 'index', flatten = True ) ], description = 'Scatter Plot Index' ) def _value_default ( self ): """ Returns the default value for the 'value' trait. """ return TemplateDataSource( items = [ ValueDataNameItem( name = 'value', flatten = True ) ], description = 'Scatter Plot Value' ) #-- ITemplate Interface Implementation ------------------------------------- def activate_template ( self ): """ Converts all contained 'TDerived' objects to real objects using the template traits of the object. This method must be overridden in subclasses. Returns ------- None """ # If our data sources are still unbound, then just exit; someone must # have marked them as optional: if ((self.index.context_data is Undefined) or (self.value.context_data is Undefined)): return # Create a plot data object and give it this data: pd = ArrayPlotData() pd.set_data( 'index', self.index.context_data ) pd.set_data( 'value', self.value.context_data ) # Create the plot: self.plot = plot = Plot( pd ) plot.plot( ( 'index', 'value' ), type = 'scatter', index_sort = 'ascending', marker = self.marker, color = self.color, outline_color = self.outline_color, marker_size = self.marker_size, line_width = self.line_width, bgcolor = 'white' ) plot.set( padding_left = 50, padding_right = 0, padding_top = 0, padding_bottom = 20 ) # Attach some tools to the plot: plot.tools.append( PanTool( plot, constrain_key = 'shift' ) ) zoom = SimpleZoom( component = plot, tool_mode = 'box', always_on = False ) plot.overlays.append( zoom ) #-- Trait Event Handlers --------------------------------------------------- def _update_changed ( self ): """ Handles a plot option being changed. """ self.plot = Undefined apptools-4.1.0/apptools/template/test/template_view.py0000644000175100001440000003071111674464005024221 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # A feature-based Traits UI plug-in for viewing templates. # # Written by: David C. Morrill # # Date: 07/30/2007 # # (c) Copyright 2007 by Enthought, Inc. # #------------------------------------------------------------------------------- """ A feature-based Traits UI plug-in for viewing templates. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- import sys from os.path \ import split, splitext from pickle \ import dump, load from traits.api \ import HasPrivateTraits, Str, Instance, Any, File, Button, on_trait_change from traitsui.api \ import View, VGroup, HGroup, Tabbed, Item, InstanceEditor, Theme, Label from traitsui.wx.themed_text_editor \ import ThemedTextEditor from etsdevtools.developer.features.api \ import DropFile from pyface.api \ import FileDialog, OK from etsdevtools.developer.helper.themes \ import TButton from blockcanvas.app.utils \ import import_log_files from apptools.template.api \ import ITemplate, ITemplateDataContext, TemplateDataNames, Template from enable_editor \ import EnableEditor #-- Adapters that might be used ------------------------------------------------ from apptools.template.impl.base_data_context_adapter \ import BaseDataContextAdapter #------------------------------------------------------------------------------- # Helper class: #------------------------------------------------------------------------------- class Message ( HasPrivateTraits ): #-- Trait Definitions ------------------------------------------------------ # The message to be displayed: message = Str #-- Traits View Definitions ------------------------------------------------ view = View( Item( 'message', style = 'readonly', show_label = False, editor = ThemedTextEditor( theme = '@GBB' ), ) ) #-- Object Overrides ------------------------------------------------------- def __init__ ( self, message = '', **traits ): traits.setdefault( 'message', message ) super( Message, self ).__init__( **traits ) # Define some standard messages: no_context = Message( 'Please provide a data context' ) no_template = Message( 'Please provide a view template' ) no_template_found = Message( 'No view templates found in Python source file' ) no_bindings = Message( 'Please resolve all required template data bindings' ) no_options = Message( 'No options are currently available' ) #------------------------------------------------------------------------------- # 'TemplateView' class: #------------------------------------------------------------------------------- class TemplateView ( HasPrivateTraits ): """ A feature-based Traits UI plug-in for viewing templates. """ #-- Public Traits ---------------------------------------------------------- # The name of the plugin: name = Str( 'Template View' ) # The data context supplying the data to be viewed: context = Instance( ITemplateDataContext, connect = 'to: data context' ) # The name of the file containing a template view: file_name = File( drop_file = DropFile( extensions = [ '.py', '.tv', '.las' ], tooltip = 'Drop a LAS data file, a saved view template ' 'or a Python source file containing a view ' 'template here.' ), connect = 'to: template view' ) # The template to view: template = Instance( ITemplate ) #-- Private Traits --------------------------------------------------------- # The name of the file to save the template in: save_file_name = File # The current data names: data_names = Instance( TemplateDataNames ) # The TemplateDataNames or Message being viewed: names_view = Any( no_context ) # The name of the most recently loaded template file: template_file_name = File # The template or message being viewed: template_view = Any( no_template ) # The options view for the currently active template: options_view = Any( no_options ) # The event fired when the user wants to save the template: save_template = Button( 'Save Template' ) #-- Traits View Definitions ------------------------------------------------ view = View( VGroup( HGroup( Item( 'file_name', show_label = False, width = 350 ), TButton( 'save_template', label = 'Save Template', enabled_when = 'template is not None' ), group_theme = Theme( '@GFB', margins = ( -7, -5 ) ) ), Tabbed( VGroup( '8', Label( 'Data Bindings', item_theme = Theme( '@GBB', alignment = 'center' ) ), Item( 'names_view', style = 'custom', resizable = True, editor = InstanceEditor(), export = 'DockWindowShell', item_theme = Theme( '@GFB', margins = ( -5, -1 ) ) ), label = 'Data Bindings', show_labels = False ), Item( 'template_view', label = 'Template View', style = 'custom', resizable = True, editor = InstanceEditor( view = 'template_view' ), export = 'DockWindowShell' ), Item( 'options_view', label = 'Template Options', style = 'custom', resizable = True, editor = InstanceEditor( view = 'options_view' ), export = 'DockWindowShell' ), id = 'tabbed', dock = 'horizontal', show_labels = False ), ), id = 'template.test.template_view.TemplateView' ) #-- Trait Event Handlers --------------------------------------------------- def _context_changed ( self, context ): """ Handles the 'context' trait being changed. """ if context is None: self.names_view = no_context elif self.template is None: self.names_view = no_template else: self._create_view() def _template_changed ( self, template ): """ Handles the 'template' trait being changed. """ if self.context is None: self.template_view = no_context else: self._create_view() def _file_name_changed ( self, file_name ): """ Handles the 'file_name' trait being changed. """ ext = splitext( file_name )[1] if ext == '.py': self._load_python_template( file_name ) elif ext == '.tv': self._load_pickled_template( file_name ) elif ext == '.las': self._load_las_file( file_name ) else: # fixme: Display an informational message here... pass def _save_template_changed ( self ): """ Handles the user clicking the 'Save Template' button. """ self._save_pickled_template() @on_trait_change( 'data_names.unresolved_data_names' ) def _on_unresolved_data_names ( self ): if len( self.data_names.unresolved_data_names ) == 0: self._create_object_view() elif not isinstance( self.template_view, Message ): self.template_view = no_bindings self.options_view = no_options @on_trait_change( 'template.template_mutated?' ) def _on_template_mutated ( self ): """ Handles a mutable template changing. """ if self.context is not None: self._create_view() #-- Private Methods -------------------------------------------------------- def _load_python_template ( self, file_name ): """ Attempts to load a template from a Python source file. """ path, name = split( file_name ) sys.path[0:0] = [ path ] try: ###values = {} module_name, ext = splitext( name ) module = __import__( module_name ) values = module.__dict__ ###execfile( file_name, values ) template = values.get( 'template' ) if template is None: templates = [] for value in values.values(): try: if (issubclass( value, Template ) and ###(value.__module__ == '__builtin__')): (value.__module__ == module_name)): templates.append( value ) except: pass for i, template in enumerate( templates ): for t in templates[ i + 1: ]: if issubclass( template, t ): break else: break else: self.template_view = no_template_found return if not isinstance( template, Template ): template = template() self.template = template self.template_file_name = file_name except Exception, excp: self.template_view = Message( str( excp ) ) # Clean up the Python path: del sys.path[0] def _load_pickled_template ( self, file_name ): """ Attempts to load a template from a pickle. """ # fixme: Implement this...load template from .tv pickle file. fh = None delete = False try: fh = open( file_name, 'rb' ) file_name = load( fh ) path, name = split( file_name ) sys.path[0:0] = [ path ] delete = True module_name, ext = splitext( name ) module = __import__( module_name ) self.template = load( fh ) self.template_file_name = file_name except Exception, excp: import traceback traceback.print_exc() self.template_view = Message( str( excp ) ) if fh is not None: fh.close() if delete: del sys.path[0] def _load_las_file ( self, file_name ): """ Creates a data context from the specified LAS file. """ try: self.context = import_log_files( file_name, 'las' ) except Exception, excp: self.names_view = Message( str( excp ) ) def _save_pickled_template ( self ): file_name = self.save_file_name or self.file_name fd = FileDialog( action = 'save as', default_path = file_name ) #wildcard = 'Template files (*.tv)|*.tv|' ) if fd.open() == OK: self.save_file_name = file_name = fd.path fh = None try: fh = open( file_name, 'wb' ) dump( self.template_file_name, fh, -1 ) dump( self.template.template_from_object(), fh, -1 ) except: # fixme: Display an informational message here... import traceback traceback.print_exc() if fh is not None: fh.close() def _create_view ( self ): """ Begins the process of creating a live view from a template and data context object. """ self.data_names = self.names_view = nv = TemplateDataNames( context = self.context, data_names = self.template.names_from_template() ) if len( nv.unresolved_data_names ) == 0: self._create_object_view() else: self.template_view = no_bindings def _create_object_view ( self ): """ Create the object view from the current template. """ self.template.object_from_template() self.template_view = self.options_view = self.template apptools-4.1.0/apptools/template/test/__init__.py0000644000175100001440000000000111674464005023100 0ustar ischnellusers00000000000000 apptools-4.1.0/apptools/template/test/scatter_plot_2.py0000644000175100001440000001420711674464005024302 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # A simple dual scatter-plot template defined as a test for the template # package. # # Written by: David C. Morrill # (based on the original cp.plot geo_scatter_plot.py file) # # Date: 08/01/2007 # # (c) Copy 2007 by Enthought, Inc. # #------------------------------------------------------------------------------- """ A simple dual scatter-plot template defined as a test for the template package. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- from traits.api \ import Undefined from traitsui.api \ import View, VGroup, Item, Label, Theme, TextEditor from traitsui.wx.themed_slider_editor \ import ThemedSliderEditor from traitsui.wx.themed_text_editor \ import ThemedTextEditor from enable.api \ import ColorTrait from chaco.api \ import HPlotContainer from chaco.scatter_markers \ import marker_trait from apptools.template.api \ import Template, TRange, TStr, TInstance, TDerived from enable_editor \ import EnableEditor from scatter_plot \ import ScatterPlot #------------------------------------------------------------------------------- # Trait definitions: #------------------------------------------------------------------------------- # Template color trait: TColor = ColorTrait( template = 'copy' ) #------------------------------------------------------------------------------- # 'ScatterPlot2' class: #------------------------------------------------------------------------------- class ScatterPlot2 ( Template ): #-- Template Traits -------------------------------------------------------- # The title of the plot: title = TStr( 'Dual Scatter Plots' ) # The type of marker to use. This is a mapped trait using strings as the # keys: marker = marker_trait( template = 'copy', event = 'update' ) # The pixel size of the marker (doesn't include the thickness of the # outline): marker_size = TRange( 1, 5, 1, event = 'update' ) # The thickness, in pixels, of the outline to draw around the marker. If # this is 0, no outline will be drawn. line_width = TRange( 0.0, 5.0, 1.0 ) # The fill color of the marker: color = TColor( 'red', event = 'update' ) # The color of the outline to draw around the marker outline_color = TColor( 'black', event = 'update' ) # The amount of space between plots: spacing = TRange( 0.0, 20.0, 0.0 ) # The contained scatter plots: scatter_plot_1 = TInstance( ScatterPlot, () ) scatter_plot_2 = TInstance( ScatterPlot, () ) #-- Derived Traits --------------------------------------------------------- plot = TDerived #-- Traits UI Views -------------------------------------------------------- # The scatter plot view: template_view = View( VGroup( Item( 'title', show_label = False, style = 'readonly', editor = ThemedTextEditor( theme = Theme( '@GBB', alignment = 'center' ) ) ), Item( 'plot', show_label = False, resizable = True, editor = EnableEditor(), item_theme = Theme( '@GF5', margins = 0 ) ) ), resizable = True ) # The scatter plot options view: options_view = View( VGroup( VGroup( Label( 'Scatter Plot Options', item_theme = Theme( '@GBB', alignment = 'center' ) ), show_labels = False ), VGroup( Item( 'title', editor = TextEditor() ), Item( 'marker' ), Item( 'marker_size', editor = ThemedSliderEditor() ), Item( 'line_width', label = 'Line Width', editor = ThemedSliderEditor() ), Item( 'spacing', editor = ThemedSliderEditor() ), Item( 'color', label = 'Fill Color' ), Item( 'outline_color', label = 'Outline Color' ), group_theme = Theme( '@GF5', margins = ( -5, -1 ) ), item_theme = Theme( '@G0B', margins = 0 ) ) ) ) #-- ITemplate Interface Implementation ------------------------------------- def activate_template ( self ): """ Converts all contained 'TDerived' objects to real objects using the template traits of the object. This method must be overridden in subclasses. Returns ------- None """ plots = [ p for p in [ self.scatter_plot_1.plot, self.scatter_plot_2.plot ] if p is not None ] if len( plots ) == 2: self.plot = HPlotContainer( spacing = self.spacing ) self.plot.add( *plots ) elif len( plots ) == 1: self.plot = plots[0] #-- Default Values --------------------------------------------------------- def _scatter_plot_1_default ( self ): """ Returns the default value for the first scatter plot. """ result = ScatterPlot() result.index.description = 'Shared Plot Index' result.value.description += ' 1' return result def _scatter_plot_2_default ( self ): """ Returns the default value for the second scatter plot. """ result = ScatterPlot( index = self.scatter_plot_1.index ) result.value.description += ' 2' result.value.optional = True return result #-- Trait Event Handlers --------------------------------------------------- def _update_changed ( self, name, old, new ): """ Handles a plot option being changed. """ setattr( self.scatter_plot_1, name, new ) setattr( self.scatter_plot_2, name, new ) self.plot = Undefined def _spacing_changed ( self, spacing ): """ Handles the spacing between plots being changed. """ self.plot = Undefined apptools-4.1.0/apptools/persistence/0000755000175100001440000000000011674464005020532 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/persistence/file_path.py0000644000175100001440000000517111674464005023043 0ustar ischnellusers00000000000000"""Simple class to support file path objects that work well in the context of persistent storage with the state_pickler. """ # Author: Prabhu Ramachandran # Copyright (c) 2005, Enthought, Inc. # License: BSD Style. # Standard library imports. import os from os.path import abspath, normpath, dirname, commonprefix, join class FilePath(object): """This class stores two paths to the file. A relative path and an absolute one. The absolute path is used by the end user. When this object is pickled the state_pickler sets the relative path relative to the file that is being generated. When unpickled, the stored relative path is used to set the absolute path correctly based on the path of the saved file. """ def __init__(self, value=''): self.set(value) def __str__(self): return self.abs_pth def __repr__(self): return self.abs_pth.__repr__() def get(self): """Get the path. """ return self.abs_pth def set(self, value): """Sets the value of the path. """ self.rel_pth = value if value: self.abs_pth = normpath(abspath(value)) else: self.abs_pth = '' def set_relative(self, base_f_name): """Sets the path relative to `base_f_name`. Note that `base_f_name` and self.rel_pth should be valid file names correct on the current os. The set name is a file name that has a POSIX path. """ # Get normalized paths. _src = abspath(base_f_name) _dst = self.abs_pth # Now strip out any common prefix between the two paths. for part in _src.split(os.sep): if _dst.startswith(part+os.sep): length = len(part) + 1 _src = _src[length:] _dst = _dst[length:] else: break # For each directory in the source, we need to add a reference to # the parent directory to the destination. ret = (_src.count(os.sep) * ('..' + os.sep)) + _dst # Make it posix style. if os.sep is not '/': ret.replace(os.sep, '/') # Store it. self.rel_pth = ret def set_absolute(self, base_f_name): """Sets the absolute file name for the current relative file name with respect to the given `base_f_name`. """ base_f_name = normpath(abspath(base_f_name)) rel_file_name = normpath(self.rel_pth) file_name = join(dirname(base_f_name), rel_file_name) file_name = os.path.normpath(file_name) self.abs_pth = file_name apptools-4.1.0/apptools/persistence/version_registry.py0000644000175100001440000000663611674464005024534 0ustar ischnellusers00000000000000"""A version registry that manages handlers for different state versions. """ # Author: Prabhu Ramachandran # Copyright (c) 2005, Enthought, Inc. # License: BSD Style. # Standard library imports. import sys import inspect import logging logger = logging.getLogger(__name__) ###################################################################### # Utility functions. ###################################################################### def get_version(obj): """Walks the class hierarchy and obtains the versions of the various classes and returns a list of tuples of the form ((class_name, module), version) in reverse order of the MRO. """ res = [] for cls in inspect.getmro(obj.__class__): class_name, module = cls.__name__, cls.__module__ if module in ['__builtin__']: # No point in versioning builtins. continue try: version = cls.__version__ except AttributeError: version = -1 res.append( ( (class_name, module), version) ) res.reverse() return res ###################################################################### # `HandlerRegistry` class. ###################################################################### class HandlerRegistry: """A simple version conversion handler registry. Classes register handlers in order to convert the state version to the latest version. When an object's state is about to be set, the `update` method of the registy is called. This in turn calls any handlers registered for the class/module and this handler is then called with the state and the version of the state. The state is modified in-place by the handlers. """ def __init__(self): # The version conversion handlers. # Key: (class_name, module), value: handler self.handlers = {} def register(self, class_name, module, handler): """Register `handler` that handles versioning for class having class name (`class_name`) and module name (`module`). The handler function will be passed the state and its version to fix. """ key = (class_name, module) if key in self.handlers: msg = 'Overwriting version handler for (%s, %s)'%(key[0], key[1]) logger.warn(msg) self.handlers[(class_name, module)] = handler def unregister(self, class_name, module): """Unregisters any handlers for a class and module. """ self.handlers.pop((class_name, module)) def update(self, state): """Updates the given state using the handlers. Note that the state is modified in-place. """ if (not self.handlers) or (not hasattr(state, '__metadata__')): return versions = state.__metadata__['version'] for ver in versions: key = ver[0] try: self.handlers[key](state, ver[1]) except KeyError: pass def _create_registry(): """Creates a reload safe, singleton registry. """ registry = None for key in sys.modules.keys(): if 'version_registry' in key: mod = sys.modules[key] if hasattr(mod, 'registry'): registry = mod.registry break if not registry: registry = HandlerRegistry() return registry # The singleton registry. registry = _create_registry() apptools-4.1.0/apptools/persistence/project_loader.py0000644000175100001440000000741411674464005024106 0ustar ischnellusers00000000000000 # Standard library imports import sys import pickle import logging # Enthought library imports from apptools.persistence.versioned_unpickler import VersionedUnpickler logger = logging.getLogger(__name__) def load_project(pickle_filename, updater_path, application_version, protocol, max_pass=-1): """ Reads a project from a pickle file and if necessary will update it to the latest version of the application. """ latest_file = pickle_filename # Read the pickled project's metadata. f = file(latest_file, 'rb') metadata = VersionedUnpickler(f).load(max_pass) f.close() project_version = metadata.get('version', False) if not project_version: raise ValueError, "Could not read version number from the project file" logger.debug('Project version: %d, Application version: %d' % (project_version, application_version)) # here you can temporarily force an upgrade each time for testing .... # project_version = 0 latest_file = upgrade_project(pickle_filename, updater_path, project_version, application_version, protocol, max_pass) # Finally we can import the project ... logger.info('loading %s' % latest_file) i_f = file(latest_file, 'rb') version = VersionedUnpickler(i_f).load(max_pass) project = VersionedUnpickler(i_f).load(max_pass) i_f.close() return project def upgrade_project(pickle_filename, updater_path, project_version, application_version, protocol, max_pass=-1): """ Repeatedly read and write the project to disk updating it one version at a time. Example the p5.project is at version 0 The application is at version 3 p5.project --- Update1 ---> p5.project.v1 p5.project.v1 --- Update2 ---> p5.project.v2 p5.project.v2 --- Update3 ---> p5.project.v3 p5.project.v3 ---> loaded into app The user then has the option to save the updated project as p5.project """ first_time = True latest_file = pickle_filename # update the project until it's version matches the application's while project_version < application_version: next_version = project_version + 1 if first_time: i_f = file(pickle_filename, 'rb') data = i_f.read() open('%s.bak' % pickle_filename, 'wb').write(data) i_f.seek(0) # rewind the file to the start else: name = '%s.v%d' % (pickle_filename, project_version) i_f = file(name, 'rb') latest_file = name logger.info('converting %s' % latest_file) # find this version's updater ... updater_name = '%s.update%d' % (updater_path, next_version) __import__(updater_name) mod = sys.modules[updater_name] klass = getattr(mod, 'Update%d' % next_version) updater = klass() # load and update this version of the project version = VersionedUnpickler(i_f).load(max_pass) project = VersionedUnpickler(i_f, updater).load(max_pass) i_f.close() # set the project version to be the same as the updater we just # ran on the unpickled files ... project.metadata['version'] = next_version # Persist the updated project ... name = '%s.v%d' % (pickle_filename, next_version) latest_file = name o_f = file(name, 'wb') pickle.dump(project.metadata, o_f, protocol=protocol) pickle.dump(project, o_f, protocol=protocol) o_f.close() # Bump up the version number of the pickled project... project_version += 1 first_time = False return latest_file ### EOF ################################################################# apptools-4.1.0/apptools/persistence/tests/0000755000175100001440000000000011674464005021674 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/persistence/tests/test_spickle.py0000644000175100001440000000364611674464005024750 0ustar ischnellusers00000000000000"""Test for the spickle module. """ # Author: Prabhu Ramachandran # Copyright (c) 2007, Enthought, Inc. # License: BSD Style. import unittest import numpy from pickle import dumps from apptools.persistence import spickle from traits.api import HasTraits, Float, Int class A: def __init__(self): self.a = 100 self.array = numpy.linspace(0, 1, 5) class B(HasTraits): i = Int(10) f = Float(1.0) class Foo(object): def __init__(self, a=1): self.a = A() self.a.b = 200 self.ref = self.a self.b = B() self.b.set(i=20, f=2.0) class TestStatePickler(unittest.TestCase): def _test_object(self, x): assert x.a.a == 100 assert numpy.all(x.a.array == numpy.linspace(0, 1, 5)) assert x.a.b == 200 assert x.a == x.ref assert x.b.i == 20 assert x.b.f == 2.0 def test_dump_state(self): "Test if we are able to dump the state to a string." f = Foo() str = dumps(f) st = spickle.get_state(f) str1 = spickle.dumps_state(st) self.assertEqual(str, str1) st = spickle.loads_state(str) self.assertEqual(str, spickle.dumps_state(st)) def test_state2object(self): "Test if we can convert a state to an object." f = Foo() str = dumps(f) st = spickle.get_state(f) g = spickle.state2object(st) self._test_object(g) def test_suite(): """Collects all the tests to be run.""" suites = [] suites.append(unittest.makeSuite(TestStatePickler, 'test_')) total_suite = unittest.TestSuite(suites) return total_suite def test(verbose=2): """Useful when you need to run the tests interactively.""" all_tests = test_suite() runner = unittest.TextTestRunner(verbosity=verbose) result = runner.run(all_tests) return result, runner if __name__ == "__main__": unittest.main() apptools-4.1.0/apptools/persistence/tests/test_state_pickler.py0000644000175100001440000003331411674464005026142 0ustar ischnellusers00000000000000"""Unit tests for the state pickler and unpickler. """ # Author: Prabhu Ramachandran # Copyright (c) 2005, Enthought, Inc. # License: BSD Style. import unittest import math import numpy from traits.api import Bool, Int, Long, Array, Float, Complex, Any, \ Str, Unicode, Instance, Tuple, List, Dict, HasTraits from tvtk.api import tvtk from apptools.persistence import state_pickler # A simple class to test instances. class A: def __init__(self): self.a = 'a' # A classic class for testing the pickler. class TestClassic: def __init__(self): self.b = False self.i = 7 self.l = 1234567890123456789 self.f = math.pi self.c = complex(1.01234, 2.3) self.n = None self.s = 'String' self.u = u'Unicode' self.inst = A() self.tuple = (1,2,'a', A()) self.list = [1, 1.1, 'a', 1j, self.inst] self.pure_list = range(5) self.dict = {'a': 1, 'b': 2, 'ref': self.inst} self.numeric = numpy.ones((2,2,2), 'f') self.ref = self.numeric self._tvtk = tvtk.Property() # A class with traits for testing the pickler. class TestTraits(HasTraits): b = Bool(False) i = Int(7) l = Long(1234567890123456789L) f = Float(math.pi) c = Complex(complex(1.01234, 2.3)) n = Any s = Str('String') u = Unicode(u'Unicode') inst = Instance(A) tuple = Tuple list = List pure_list = List(range(5)) dict = Dict numeric = Array(value=numpy.ones((2,2,2), 'f')) ref = Array _tvtk = Instance(tvtk.Property, ()) def __init__(self): self.inst = A() self.tuple = (1,2,'a', A()) self.list = [1, 1.1, 'a', 1j, self.inst] self.dict = {'a': 1, 'b': 2, 'ref': self.inst} self.ref = self.numeric class TestDictPickler(unittest.TestCase): def set_object(self, obj): """Changes the objects properties to test things.""" obj.b = True obj.i = 8 obj.s = 'string' obj.u = u'unicode' obj.inst.a = 'b' obj.list[0] = 2 obj.tuple[-1].a = 't' obj.dict['a'] = 10 obj._tvtk.set(point_size=3, specular_color=(1, 0, 0), representation='w') def verify(self, obj, state): data = state['data'] self.assertEqual(state['class_name'], obj.__class__.__name__) data = data['data'] self.assertEqual(data['b'], obj.b) self.assertEqual(data['i'], obj.i) self.assertEqual(data['l'], obj.l) self.assertEqual(data['f'], obj.f) self.assertEqual(data['c'], obj.c) self.assertEqual(data['n'], obj.n) self.assertEqual(data['s'], obj.s) self.assertEqual(data['u'], obj.u) if isinstance(obj, HasTraits): self.assertEqual(data['inst']['type'], 'reference') else: self.assertEqual(data['inst']['type'], 'reference') tup = data['tuple']['data'] self.assertEqual(tup[:-1], obj.tuple[:-1]) self.assertEqual(tup[-1]['data']['data']['a'], 't') lst = data['list']['data'] self.assertEqual(lst[:-1], obj.list[:-1]) if isinstance(obj, HasTraits): self.assertEqual(lst[-1]['type'], 'instance') else: self.assertEqual(lst[-1]['data']['data']['a'], 'b') pure_lst = data['pure_list']['data'] self.assertEqual(pure_lst, obj.pure_list) dct = data['dict']['data'] self.assertEqual(dct['a'], obj.dict['a']) self.assertEqual(dct['b'], obj.dict['b']) self.assertEqual(dct['ref']['type'], 'reference') junk = state_pickler.gunzip_string(data['numeric']['data'].decode('base64')) num = numpy.loads(junk) self.assertEqual(numpy.alltrue(numpy.ravel(num == obj.numeric)), 1) self.assertEqual(data['ref']['type'], 'reference') self.assertEqual(data['ref']['id'], data['numeric']['id']) def verify_unpickled(self, obj, state): self.assertEqual(state.__metadata__['class_name'], obj.__class__.__name__) self.assertEqual(state.b, obj.b) self.assertEqual(state.i, obj.i) self.assertEqual(state.l, obj.l) self.assertEqual(state.f, obj.f) self.assertEqual(state.c, obj.c) self.assertEqual(state.n, obj.n) self.assertEqual(state.s, obj.s) self.assertEqual(state.u, obj.u) self.assertEqual(state.inst.__metadata__['type'], 'instance') tup = state.tuple self.assertEqual(state.tuple.has_instance, True) self.assertEqual(tup[:-1], obj.tuple[:-1]) self.assertEqual(tup[-1].a, 't') lst = state.list self.assertEqual(state.list.has_instance, True) self.assertEqual(lst[:-1], obj.list[:-1]) # Make sure the reference is the same self.assertEqual(id(state.inst), id(lst[-1])) self.assertEqual(lst[-1].a, 'b') pure_lst = state.pure_list self.assertEqual(pure_lst, obj.pure_list) self.assertEqual(state.pure_list.has_instance, False) dct = state.dict self.assertEqual(dct.has_instance, True) self.assertEqual(dct['a'], obj.dict['a']) self.assertEqual(dct['b'], obj.dict['b']) self.assertEqual(dct['ref'].__metadata__['type'], 'instance') num = state.numeric self.assertEqual(numpy.alltrue(numpy.ravel(num == obj.numeric)), 1) self.assertEqual(id(state.ref), id(num)) _tvtk = state._tvtk self.assertEqual(_tvtk.representation, obj._tvtk.representation) self.assertEqual(_tvtk.specular_color, obj._tvtk.specular_color) self.assertEqual(_tvtk.point_size, obj._tvtk.point_size) def verify_state(self, state1, state): self.assertEqual(state.__metadata__, state1.__metadata__) self.assertEqual(state.b, state1.b) self.assertEqual(state.i, state1.i) self.assertEqual(state.l, state1.l) self.assertEqual(state.f, state1.f) self.assertEqual(state.c, state1.c) self.assertEqual(state.n, state1.n) self.assertEqual(state.s, state1.s) self.assertEqual(state.u, state1.u) self.assertEqual(state.inst.__metadata__, state1.inst.__metadata__) self.assertEqual(state.tuple, state1.tuple) lst = state.list self.assertEqual(state.list, state1.list) self.assertEqual(state.pure_list, state1.pure_list) dct = state.dict self.assertEqual(state.dict, state1.dict) num = state.numeric self.assertEqual((state1.numeric ==state.numeric).all(), True) self.assertEqual(id(state.ref), id(state.numeric)) self.assertEqual(id(state1.ref), id(state1.numeric)) # The ID's need not be identical so we equate them here so the # tests pass. Note that the ID's only need be consistent not # identical! state1._tvtk.__metadata__['id'] = state._tvtk.__metadata__['id'] self.assertEqual(state1._tvtk, state._tvtk) def test_has_instance(self): """Test to check has_instance correctness.""" a = A() r = state_pickler.get_state(a) self.assertEqual(r.__metadata__['has_instance'], True) l = [1, a] r = state_pickler.get_state(l) self.assertEqual(r.has_instance, True) self.assertEqual(r[1].__metadata__['has_instance'], True) d = {'a': l, 'b':1} r = state_pickler.get_state(d) self.assertEqual(r.has_instance, True) self.assertEqual(r['a'].has_instance, True) self.assertEqual(r['a'][1].__metadata__['has_instance'], True) class B: def __init__(self): self.a = [1, A()] b = B() r = state_pickler.get_state(b) self.assertEqual(r.__metadata__['has_instance'], True) self.assertEqual(r.a.has_instance, True) self.assertEqual(r.a[1].__metadata__['has_instance'], True) def test_pickle_classic(self): """Test if classic classes can be pickled.""" t = TestClassic() self.set_object(t) # Generate the dict that is actually pickled. state = state_pickler.StatePickler().dump_state(t) # First check if all the attributes are handled. keys = state['data']['data'].keys() keys.sort() expect = [x for x in t.__dict__.keys() if '__' not in x] expect.sort() self.assertEqual(keys, expect) # Check each attribute. self.verify(t, state) def test_unpickle_classic(self): """Test if classic classes can be unpickled.""" t = TestClassic() self.set_object(t) # Get the pickled state. res = state_pickler.get_state(t) # Check each attribute. self.verify_unpickled(t, res) def test_state_setter_classic(self): """Test if classic classes' state can be set.""" t = TestClassic() self.set_object(t) # Get the pickled state. res = state_pickler.get_state(t) # Now create a new instance and set its state. t1 = state_pickler.create_instance(res) state_pickler.set_state(t1, res) # Check each attribute. self.verify_unpickled(t1, res) def test_state_setter(self): """Test some of the features of the set_state method.""" t = TestClassic() self.set_object(t) # Get the saved state. res = state_pickler.get_state(t) # Now create a new instance and test the setter. t1 = state_pickler.create_instance(res) keys = ['c', 'b', 'f', 'i', 'tuple', 'list', 'l', 'numeric', 'n', 's', 'u', 'pure_list', 'inst', 'ref', 'dict'] ignore = list(keys) ignore.remove('b') first = ['b'] last = [] state_pickler.set_state(t1, res, ignore=ignore, first=first, last=last) # Only 'b' should have been set. self.assertEqual(t1.b, True) # Rest are unchanged. self.assertEqual(t1.i, 7) self.assertEqual(t1.s, 'String') self.assertEqual(t1.u, u'Unicode') self.assertEqual(t1.inst.a, 'a') self.assertEqual(t1.list[0], 1) self.assertEqual(t1.tuple[-1].a, 'a') self.assertEqual(t1.dict['a'], 1) # Check if last works. last = ignore ignore = [] first = [] state_pickler.set_state(t1, res, ignore=ignore, first=first, last=last) # Check everything. self.verify_unpickled(t1, res) def test_pickle_traits(self): """Test if traited classes can be pickled.""" t = TestTraits() self.set_object(t) # Generate the dict that is actually pickled. state = state_pickler.StatePickler().dump_state(t) # First check if all the attributes are handled. keys = state['data']['data'].keys() keys.sort() expect = [x for x in t.__dict__.keys() if '__' not in x] expect.sort() self.assertEqual(keys, expect) # Check each attribute. self.verify(t, state) def test_unpickle_traits(self): """Test if traited classes can be unpickled.""" t = TestTraits() self.set_object(t) # Get the pickled state. res = state_pickler.get_state(t) # Check each attribute. self.verify_unpickled(t, res) def test_state_setter_traits(self): """Test if traited classes' state can be set.""" t = TestTraits() self.set_object(t) # Get the saved state. res = state_pickler.get_state(t) # Now create a new instance and set its state. t1 = state_pickler.create_instance(res) state_pickler.set_state(t1, res) # Check each attribute. self.verify_unpickled(t1, res) def test_reference_cycle(self): """Test if reference cycles are handled when setting the state.""" class A: pass class B: pass a = A() b = B() a.a = b b.b = a state = state_pickler.get_state(a) z = A() z.a = B() z.a.b = z state_pickler.set_state(z, state) def test_state_is_saveable(self): """Test if the state can be saved like the object itself.""" t = TestClassic() self.set_object(t) state = state_pickler.get_state(t) # Now get the state of the state itself. state1 = state_pickler.get_state(state) self.verify_state(state1, state) # Same thing for the traited class. t = TestTraits() self.set_object(t) state = state_pickler.get_state(t) # Now get the state of the state itself. state1 = state_pickler.get_state(state) self.verify_state(state1, state) def test_get_pure_state(self): """Test if get_pure_state is called first.""" class B: def __init__(self): self.a = 'dict' def __get_pure_state__(self): return {'a':'get_pure_state'} def __getstate__(self): return {'a':'getstate'} b = B() s = state_pickler.get_state(b) self.assertEqual(s.a, 'get_pure_state') del B.__get_pure_state__ s = state_pickler.get_state(b) self.assertEqual(s.a, 'getstate') del B.__getstate__ s = state_pickler.get_state(b) self.assertEqual(s.a, 'dict') def test_suite(): """Collects all the tests to be run.""" suites = [] suites.append(unittest.makeSuite(TestDictPickler, 'test_')) total_suite = unittest.TestSuite(suites) return total_suite def test(verbose=2): """Useful when you need to run the tests interactively.""" all_tests = test_suite() runner = unittest.TextTestRunner(verbosity=verbose) result = runner.run(all_tests) return result, runner if __name__ == "__main__": unittest.main() apptools-4.1.0/apptools/persistence/tests/check_version_registry.py0000644000175100001440000000700311674464005027020 0ustar ischnellusers00000000000000"""Tests for the version registry. """ # Author: Prabhu Ramachandran # Copyright (c) 2005, Enthought, Inc. # License: BSD Style. # Standard library imports. import unittest # Enthought library imports. from traits.api import HasTraits from apptools.persistence import version_registry, state_pickler class Classic: __version__ = 0 class New(object): __version__ = 0 class TraitClass(HasTraits): __version__ = 0 class Test(New): __version__ = 1 def __init__(self): self.a = Classic() class Handler: def __init__(self): self.calls = [] def upgrade(self, state, version): self.calls.append(('upgrade', state, version)) def upgrade1(self, state, version): self.calls.append(('upgrade1', state, version)) class TestVersionRegistry(unittest.TestCase): def test_get_version(self): """Test the get_version function.""" c = Classic() v = version_registry.get_version(c) res = [(('Classic', '__main__'), 0)] self.assertEqual(v, res) state = state_pickler.get_state(c) self.assertEqual(state.__metadata__['version'], res) n = New() v = version_registry.get_version(n) res = [(('New', '__main__'), 0)] self.assertEqual(v, res) state = state_pickler.get_state(n) self.assertEqual(state.__metadata__['version'], res) t = TraitClass() v = version_registry.get_version(t) res = [(('HasTraits', 'traits.has_traits'), -1), (('TraitClass', '__main__'), 0)] self.assertEqual(v, res) state = state_pickler.get_state(t) self.assertEqual(state.__metadata__['version'], res) def test_reload(self): """Test if the registry is reload safe.""" # A dummy handler. def h(x, y): pass registry = version_registry.registry registry.register('A', '__main__', h) self.assertEqual(registry.handlers.get(('A', '__main__')), h) reload(version_registry) registry = version_registry.registry self.assertEqual(registry.handlers.get(('A', '__main__')), h) del registry.handlers[('A', '__main__')] self.assertEqual(registry.handlers.has_key(('A', '__main__')), False) def test_update(self): """Test if update method calls the handlers in order.""" registry = version_registry.registry # First an elementary test. c = Classic() state = state_pickler.get_state(c) h = Handler() registry.register('Classic', '__main__', h.upgrade) c1 = state_pickler.create_instance(state) state_pickler.set_state(c1, state) self.assertEqual(h.calls, [('upgrade', state, 0)]) # Remove the handler. registry.unregister('Classic', '__main__') # Now check to see if this works for inheritance trees. t = Test() state = state_pickler.get_state(t) h = Handler() registry.register('Classic', '__main__', h.upgrade) registry.register('New', '__main__', h.upgrade) registry.register('Test', '__main__', h.upgrade1) t1 = state_pickler.create_instance(state) state_pickler.set_state(t1, state) # This should call New handler, then the Test and then # Classic. self.assertEqual(h.calls, [('upgrade', state, 0), ('upgrade1', state, 1), ('upgrade', state.a, 0)]) if __name__ == "__main__": unittest.main() apptools-4.1.0/apptools/persistence/tests/test_spawner.py0000644000175100001440000000451511674464005024771 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Ilan Schnell, Enthought, Inc. # # Description: # Unittest to run another script directly. In some cases this is # necessary when things cannot be tested with nosetests itself. #------------------------------------------------------------------------------ import sys import os.path import subprocess import unittest import tempfile from traits.util.resource import store_resource, get_path class Tests(unittest.TestCase): def setUp(self): OS_handle, fname = tempfile.mkstemp() os.close(OS_handle) self.tmpname = fname def test_file_path(self): """ Run 'check_file_path.py' as a spawned process and test return value, The python source is stored into a temporary test file before being executed in a subprocess. """ store_resource('AppTools', os.path.join('apptools', 'persistence','tests', 'check_file_path.py'), self.tmpname) retcode = subprocess.call([sys.executable, self.tmpname], cwd=get_path(Tests)) self.assertEqual(retcode, 0) def test_version_registry(self): """ Run 'check_version_registry.py' as a spawned process and test return value, The python source is stored into a temporary test file before being executed in a subprocess. """ store_resource('AppTools', os.path.join('apptools', 'persistence','tests', 'check_version_registry.py'), self.tmpname) retcode = subprocess.call([sys.executable, self.tmpname]) self.assertEqual(retcode, 0) def tearDown(self): os.unlink(self.tmpname) if __name__ == "__main__": unittest.main() apptools-4.1.0/apptools/persistence/tests/test_version_registry.py0000644000175100001440000000274511674464005026732 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Ilan Schnell, Enthought, Inc. # # Description: # Unittest to run another (unittest) script directly. # In some cases this is necessary when things cannot be tested # with nosetests itself. #------------------------------------------------------------------------------ import os import subprocess import unittest # NOTE: # `check_version_registry.py` can not be run by nosetests directly because of # namespace issues. Therefore the test in this file (which nosetests # will execute) will spawn a python interpreter running the unittest. # This test checks the return code of the process and will fail if # the return code is non-zero. class RefreshTestCase(unittest.TestCase): def test_run(self): cwd = os.path.dirname(__file__) if not cwd: cwd = '.' retcode = subprocess.call(['python', 'check_version_registry.py'], cwd=cwd) self.assertEqual(retcode, 0) if __name__ == "__main__": unittest.main() apptools-4.1.0/apptools/persistence/tests/check_file_path.py0000644000175100001440000000763711674464005025353 0ustar ischnellusers00000000000000"""Tests for the file_path module. """ # Author: Prabhu Ramachandran # Copyright (c) 2005, Enthought, Inc. # License: BSD Style. # Standard library imports. import unittest import os import sys from os.path import abspath, dirname, basename, join import StringIO # Enthought library imports. from apptools.persistence import state_pickler from apptools.persistence import file_path class Test: def __init__(self): self.f = file_path.FilePath() class TestFilePath(unittest.TestCase): def test_relative(self): """Test if relative paths are set correctly. """ fname = 't.vtk' f = file_path.FilePath(fname) cwd = os.getcwd() # Trivial case of both in same dir. f.set_relative(abspath(join(cwd, 't.mv2'))) self.assertEqual(f.rel_pth, fname) # Move one directory deeper. f.set_relative(abspath(join(cwd, 'tests', 't.mv2'))) self.assertEqual(f.rel_pth, join(os.pardir, fname)) # Move one directory shallower. f.set_relative(abspath(join(dirname(cwd), 't.mv2'))) diff = basename(cwd) self.assertEqual(f.rel_pth, join(diff, fname)) # Test where the path is relative to the root. f.set(abspath(join('data', fname))) f.set_relative('/tmp/test.mv2') if sys.platform.startswith('win'): expect = os.pardir + abspath(join('data', fname))[2:] else: expect = os.pardir + abspath(join('data', fname)) self.assertEqual(f.rel_pth, expect) def test_absolute(self): """Test if absolute paths are set corectly. """ fname = 't.vtk' f = file_path.FilePath(fname) cwd = os.getcwd() # Easy case of both in same dir. f.set_absolute(join(cwd, 'foo', 'test', 't.mv2')) self.assertEqual(f.abs_pth, join(cwd, 'foo', 'test', fname)) # One level lower. fname = join(os.pardir, 't.vtk') f.set(fname) f.set_absolute(join(cwd, 'foo', 'test', 't.mv2')) self.assertEqual(f.abs_pth, abspath(join(cwd, 'foo', 'test', fname))) # One level higher. fname = join('test', 't.vtk') f.set(fname) f.set_absolute(join(cwd, 'foo', 't.mv2')) self.assertEqual(f.abs_pth, abspath(join(cwd, 'foo', fname))) def test_pickle(self): """Test if pickler works correctly with FilePaths. """ t = Test() t.f.set('t.vtk') cwd = os.getcwd() # Create a dummy file in the parent dir. s = StringIO.StringIO() # Spoof its location. s.name = abspath(join(cwd, os.pardir, 't.mv2')) # Dump into it state_pickler.dump(t, s) # Rewind the stream s.seek(0) # "Move" the file elsewhere s.name = join(cwd, 'foo', 'test', 't.mv2') state = state_pickler.load_state(s) self.assertEqual(state.f.abs_pth, join(cwd, 'foo', 'test', 'tests', 't.vtk')) # Create a dummy file in a subdir. s = StringIO.StringIO() # Spoof its location. s.name = abspath(join(cwd, 'data', 't.mv2')) # Dump into it. state_pickler.dump(t, s) # Rewind the stream s.seek(0) # "Move" the file elsewhere s.name = join(cwd, 'foo', 'test', 't.mv2') state = state_pickler.load_state(s) self.assertEqual(state.f.abs_pth, join(cwd, 'foo', 't.vtk')) def test_suite(): """Collects all the tests to be run.""" suites = [] suites.append(unittest.makeSuite(TestFilePath, 'test_')) total_suite = unittest.TestSuite(suites) return total_suite def test(verbose=2): """Useful when you need to run the tests interactively.""" all_tests = test_suite() runner = unittest.TextTestRunner(verbosity=verbose) result = runner.run(all_tests) return result, runner if __name__ == "__main__": unittest.main() apptools-4.1.0/apptools/persistence/versioned_unpickler.py0000644000175100001440000001771011674464005025164 0ustar ischnellusers00000000000000# Standard library imports from pickle import * import sys, new import logging from types import GeneratorType # Enthought library imports from apptools.persistence.updater import __replacement_setstate__ logger = logging.getLogger(__name__) ############################################################################## # class 'NewUnpickler' ############################################################################## class NewUnpickler(Unpickler): """ An unpickler that implements a two-stage pickling process to make it possible to unpickle complicated Python object hierarchies where the unserialized state of an object depends on the state of other objects in the same pickle. """ def load(self, max_pass=-1): """Read a pickled object representation from the open file. Return the reconstituted object hierarchy specified in the file. """ # List of objects to be unpickled. self.objects = [] # We overload the load_build method. dispatch = self.dispatch dispatch[BUILD] = NewUnpickler.load_build # call the super class' method. ret = Unpickler.load(self) self.initialize(max_pass) self.objects = [] # Reset the Unpickler's dispatch table. dispatch[BUILD] = Unpickler.load_build return ret def initialize(self, max_pass): # List of (object, generator) tuples that initialize objects. generators = [] # Execute object's initialize to setup the generators. for obj in self.objects: if hasattr(obj, '__initialize__') and \ callable(obj.__initialize__): ret = obj.__initialize__() if isinstance(ret, GeneratorType): generators.append((obj, ret)) elif ret is not None: raise UnpicklingError('Unexpected return value from ' '__initialize__. %s returned %s' % (obj, ret)) # Ensure a maximum number of passes if max_pass < 0: max_pass = len(generators) # Now run the generators. count = 0 while len(generators) > 0: count += 1 if count > max_pass: not_done = [x[0] for x in generators] msg = """Reached maximum pass count %s. You may have a deadlock! The following objects are uninitialized: %s""" % (max_pass, not_done) raise UnpicklingError(msg) for o, g in generators[:]: try: g.next() except StopIteration: generators.remove((o, g)) # Make this a class method since dispatch is a class variable. # Otherwise, supposing the initial sweet_pickle.load call (which would # have overloaded the load_build method) makes a pickle.load call at some # point, we would have the dispatch still pointing to # NewPickler.load_build whereas the object being passed in will be an # Unpickler instance, causing a TypeError. def load_build(cls, obj): # Just save the instance in the list of objects. if isinstance(obj, NewUnpickler): obj.objects.append(obj.stack[-2]) Unpickler.load_build(obj) load_build = classmethod(load_build) class VersionedUnpickler(NewUnpickler): """ This class reads in a pickled file created at revision version 'n' and then applies the transforms specified in the updater class to generate a new set of objects which are at revision version 'n+1'. I decided to keep the loading of the updater out of this generic class because we will want updaters to be generated for each plugin's type of project. eg ProAVA2 projects will need different updaters to the ProAct project. This ensures that the VersionedUnpickler can remain ignorant about the actual version numbers - all it needs to do is upgrade one release. """ def __init__(self, file, updater=None): Unpickler.__init__(self, file) self.updater = updater return def find_class(self, module, name): """ Overridden method from Unpickler. NB __setstate__ is not called until later. """ if self.updater: # check to see if this class needs to be mapped to a new class # or module name original_module, original_name = module, name #logger.debug('omodule:%s oname:%s' % (original_module, original_name)) module, name = self.updater.get_latest(module, name) #logger.debug('module:%s name:%s' % (module, name)) # load the class... '''__import__(module) mod = sys.modules[module] klass = getattr(mod, name)''' klass = self.import_name(module, name) # add the updater.... TODO - why the old name? self.add_updater(original_module, original_name, klass) else: # there is no updater so we will be reading in an up to date # version of the file... try: klass = Unpickler.find_class(self, module, name) except: logger.error("Looking for [%s] [%s]" % (module, name)) logger.exception('Problem using default unpickle functionality') # restore the original __setstate__ if necessary fn = getattr(klass, '__setstate_original__', False) if fn: m = new.instancemethod(fn, None, klass) setattr(klass, '__setstate__', m) return klass def add_updater(self, module, name, klass): """ If there is an updater defined for this class we will add it to the class as the __setstate__ method. """ fn = self.updater.setstates.get((module, name), False) if fn: # move the existing __setstate__ out of the way self.backup_setstate(module, klass) # add the updater into the class m = new.instancemethod(fn, None, klass) setattr(klass, '__updater__', m) # hook up our __setstate__ which updates self.__dict__ m = new.instancemethod(__replacement_setstate__, None, klass) setattr(klass, '__setstate__', m) else: pass #print 'No updater fn to worry about' return def backup_setstate(self, module, klass): """ If the class has a user defined __setstate__ we back it up. """ if getattr(klass, '__setstate__', False): if getattr(klass, '__setstate_original__', False): # don't overwrite the original __setstate__ name = '__setstate__%s' % self.updater.__class__ else: # backup the original __setstate__ which we will restore # and run later when we have finished updating the class name = '__setstate_original__' #logger.debug('renaming __setstate__ to %s' % name) method = getattr(klass, '__setstate__') m = new.instancemethod(method, None, klass) setattr(klass, name, m) else: # the class has no __setstate__ method so do nothing pass return def import_name(self, module, name): """ If the class is needed for the latest version of the application then it should presumably exist. If the class no longer exists then we should perhaps return a proxy of the class. If the persisted file is at v1 say and the application is at v3 then objects that are required for v1 and v2 do not have to exist they only need to be placeholders for the state during an upgrade. """ #print "importing %s %s" % (name, module) module = __import__(module, globals(), locals(), [name]) return vars(module)[name] ### EOF ################################################################# apptools-4.1.0/apptools/persistence/updater.py0000644000175100001440000000226411674464005022554 0ustar ischnellusers00000000000000def __replacement_setstate__(self, state): """ """ state = self.__updater__(state) self.__dict__.update(state) return class Updater: """ An abstract class to provide functionality common to the updaters. """ def get_latest(self, module, name): """ The refactorings dictionary contains mappings between old and new module names. Since we only bump the version number one increment there is only one possible answer. """ if hasattr(self, 'refactorings'): module = self.strip(module) name = self.strip(name) # returns the new module and name if it exists otherwise defaults # to using the original module and name module, name = self.refactorings.get((module, name), (module, name)) return module, name def strip(self, string): # Who would have thought that pickle would pass us # names with \013 on the end? Is this after the files have # manually edited? if ord(string[-1:]) == 13: return string[:-1] return string #### EOF ####################################################################### apptools-4.1.0/apptools/persistence/spickle.py0000644000175100001440000002305311674464005022541 0ustar ischnellusers00000000000000"""A special unpickler that gives you a state object and a special pickler that lets you re-pickle that state. The nice thing about creating state objects is that it does not import any modules and does not create any instances. Instead of instances it creates `State` instances which have the same attributes as the real object. With this you can load a pickle (without even having the modules on the machine), modify it and re-pickle it back. NOTE: This module is not likely to work for very complex pickles but it should work for most common cases. """ # Author: Prabhu Ramachandran # Copyright (c) 2006-2007, Prabhu Ramachandran # License: BSD Style. import warnings import pickle import struct from pickle import Pickler, Unpickler, dumps, BUILD, NEWOBJ, REDUCE, \ MARK, OBJ, INST, BUILD, TupleType, PicklingError, GLOBAL, \ EXT1, EXT2, EXT4, _extension_registry, _keep_alive from cStringIO import StringIO ###################################################################### # `State` class ###################################################################### class State(dict): """Used to encapsulate the state of an instance in a very convenient form. The '__METADATA__' attribute/key is a dictionary that has class specific details like the class name, module name etc. """ def __init__(self, **kw): dict.__init__(self, **kw) self.__dict__ = self ###################################################################### # `StatePickler` class ###################################################################### class StatePickler(Pickler): """Pickles a `State` object back as a regular pickle that may be unpickled. """ def __init__(self, file, protocol, bin=None): Pickler.__init__(self, file, protocol) self.bin = bin def save(self, obj): # Check the memo x = self.memo.get(id(obj)) if x: self.write(self.get(x[0])) return if isinstance(obj, State): md = obj.__METADATA__ typ = md['type'] if typ == 'instance': self._state_instance(obj) elif typ in ['newobj', 'reduce']: self._state_reduce(obj) elif typ in ['class']: self._save_global(obj) else: Pickler.save(self, obj) def _state_instance(self, obj): md = obj.__METADATA__ cls = md.get('class') cls_md = cls.__METADATA__ memo = self.memo write = self.write save = self.save args = md.get('initargs') if len(args) > 0: _keep_alive(args, memo) write(MARK) if self.bin: save(cls) for arg in args: save(arg) write(OBJ) else: for arg in args: save(arg) write(INST + cls_md.get('module') + '\n' + cls_md.get('name') + '\n') self.memoize(obj) stuff = dict(obj.__dict__) stuff.pop('__METADATA__') if '__setstate_data__' in stuff: data = stuff.pop('__setstate_data__') _keep_alive(data, memo) save(data) else: save(stuff) write(BUILD) def _state_reduce(self, obj): # FIXME: this code is not as complete as pickle's reduce # handling code and is likely to not work in all cases. md = obj.__METADATA__ func = md.get('class') func_md = func.__METADATA__ args = md.get('initargs') state = dict(obj.__dict__) state.pop('__METADATA__') # This API is called by some subclasses # Assert that args is a tuple or None if not isinstance(args, TupleType): if args is None: # A hack for Jim Fulton's ExtensionClass, now deprecated. # See load_reduce() warnings.warn("__basicnew__ special case is deprecated", DeprecationWarning) else: raise PicklingError( "args from reduce() should be a tuple") # Assert that func is callable #if not callable(func): # raise PicklingError("func from reduce should be callable") save = self.save write = self.write # Protocol 2 special case: if func's name is __newobj__, use NEWOBJ if self.proto >= 2 and func_md.get("name", "") == "__newobj__": # FIXME: this is unlikely to work. cls = args[0] if not hasattr(cls, "__new__"): raise PicklingError( "args[0] from __newobj__ args has no __new__") if obj is not None and cls is not obj.__class__: raise PicklingError( "args[0] from __newobj__ args has the wrong class") args = args[1:] save(cls) save(args) write(NEWOBJ) else: save(func) save(args) write(REDUCE) if obj is not None: self.memoize(obj) if state is not None: if '__setstate_data__' in state: data = state.pop('__setstate_data__') save(data) else: save(state) write(BUILD) def _save_global(self, obj, name=None, pack=struct.pack): write = self.write memo = self.memo md = obj.__METADATA__ if name is None: name = md.get('name') module = md.get('module') if self.proto >= 2: code = _extension_registry.get((module, name)) if code: assert code > 0 if code <= 0xff: write(EXT1 + chr(code)) elif code <= 0xffff: write("%c%c%c" % (EXT2, code&0xff, code>>8)) else: write(EXT4 + pack(">> class A: ... def __init__(self): ... self.a = 'a' ... >>> a = A() >>> a.a = 100 >>> import state_pickler >>> s = state_pickler.dumps(a) # Dump the state of `a`. >>> state = state_pickler.loads_state(s) # Get the state back. >>> b = state_pickler.create_instance(state) # Create the object. >>> state_pickler.set_state(b, state) # Set the object's state. >>> assert b.a == 100 Features -------- - The output is a plain old dictionary so is easy to parse, edit etc. - Handles references to avoid duplication. - Gzips Numeric arrays when dumping them. - Support for versioning. Caveats ------- - Does not pickle a whole bunch of stuff including code objects and functions. - The output is a pure dictionary and does not contain instances. So using this *as it is* in `__setstate__` will not work. Instead define a `__set_pure_state__` and use the `StateSetter` class or the `set_state` function provided by this module. Notes ----- Browsing the code from XMarshaL_ and pickle.py proved useful for ideas. None of the code is taken from there though. .. _XMarshaL: http://www.dezentral.de/soft/XMarshaL """ # Author: Prabhu Ramachandran # Copyright (c) 2005, Enthought, Inc. # License: BSD Style. # Standard library imports. import sys import types import cPickle import gzip from cStringIO import StringIO import numpy # Local imports. import version_registry from file_path import FilePath NumpyArrayType = type(numpy.array([])) def gzip_string(data): """Given a string (`data`) this gzips the string and returns it. """ s = StringIO() writer = gzip.GzipFile(mode='wb', fileobj=s) writer.write(data) writer.close() s.seek(0) return s.read() def gunzip_string(data): """Given a gzipped string (`data`) this unzips the string and returns it. """ s = StringIO(data) writer = gzip.GzipFile(mode='rb', fileobj=s) data = writer.read() writer.close() return data class StatePicklerError(Exception): pass class StateUnpicklerError(Exception): pass class StateSetterError(Exception): pass ###################################################################### # `State` class ###################################################################### class State(dict): """Used to encapsulate the state of an instance in a very convenient form. The '__metadata__' attribute/key is a dictionary that has class specific details like the class name, module name etc. """ def __init__(self, **kw): dict.__init__(self, **kw) self.__dict__ = self ###################################################################### # `StateDict` class ###################################################################### class StateDict(dict): """Used to encapsulate a dictionary stored in a `State` instance. The has_instance attribute specifies if the dict has an instance embedded in it. """ def __init__(self, **kw): dict.__init__(self, **kw) self.has_instance = False ###################################################################### # `StateList` class ###################################################################### class StateList(list): """Used to encapsulate a list stored in a `State` instance. The has_instance attribute specifies if the list has an instance embedded in it. """ def __init__(self, seq=None): if seq: list.__init__(self, seq) else: list.__init__(self) self.has_instance = False ###################################################################### # `StateTuple` class ###################################################################### class StateTuple(tuple): """Used to encapsulate a tuple stored in a `State` instance. The has_instance attribute specifies if the tuple has an instance embedded in it. """ def __init__(self, seq=None): if seq: tuple.__init__(self, seq) else: tuple.__init__(self) self.has_instance = False ###################################################################### # `StatePickler` class ###################################################################### class StatePickler: """Pickles the state of an object into a dictionary. The dictionary is itself either saved as a pickled file (`dump`) or pickled string (`dumps`). Alternatively, the `dump_state` method will return the dictionary that is pickled. The format of the state dict is quite strightfoward. Basic types (bool, int, long, float, complex, None, string and unicode) are represented as they are. Everything else is stored as a dictionary containing metadata information on the object's type etc. and also the actual object in the 'data' key. For example:: >>> p = StatePickler() >>> p.dump_state(1) 1 >>> l = [1,2.0, None, [1,2,3]] >>> p.dump_state(l) {'data': [1, 2.0, None, {'data': [1, 2, 3], 'type': 'list', 'id': 1}], 'id': 0, 'type': 'list'} Classes are also represented similarly. The state in this case is obtained from the `__getstate__` method or from the `__dict__`. Here is an example:: >>> class A: ... __version__ = 1 # State version ... def __init__(self): ... self.attribute = 1 ... >>> a = A() >>> p = StatePickler() >>> p.dump_state(a) {'class_name': 'A', 'data': {'data': {'attribute': 1}, 'type': 'dict', 'id': 2}, 'id': 0, 'initargs': {'data': (), 'type': 'tuple', 'id': 1}, 'module': '__main__', 'type': 'instance', 'version': [(('A', '__main__'), 1)]} When pickling data, references are taken care of. Numeric arrays can be pickled and are stored as a gzipped base64 encoded string. """ def __init__(self): self._clear() type_map = {types.BooleanType: self._do_basic_type, types.ComplexType: self._do_basic_type, types.FloatType: self._do_basic_type, types.IntType: self._do_basic_type, types.LongType: self._do_basic_type, types.NoneType: self._do_basic_type, types.StringType: self._do_basic_type, types.UnicodeType: self._do_basic_type, types.TupleType: self._do_tuple, types.ListType: self._do_list, types.InstanceType: self._do_instance, types.DictType: self._do_dict, types.DictionaryType: self._do_dict, NumpyArrayType: self._do_numeric, State: self._do_state, } self.type_map = type_map def dump(self, value, file): """Pickles the state of the object (`value`) into the passed file. """ try: # Store the file name we are writing to so we can munge # file paths suitably. self.file_name = file.name except AttributeError: pass cPickle.dump(self._do(value), file) def dumps(self, value): """Pickles the state of the object (`value`) and returns a string. """ return cPickle.dumps(self._do(value)) def dump_state(self, value): """Returns a dictionary or a basic type representing the complete state of the object (`value`). This value is pickled by the `dump` and `dumps` methods. """ return self._do(value) ###################################################################### # Non-public methods ###################################################################### def _clear(self): # Stores the file name of the file being used to dump the # state. This is used to change any embedded paths relative # to the saved file. self.file_name = '' # Caches id's to handle references. self.obj_cache = {} # Misc cache to cache things that are not persistent. For # example, object.__getstate__()/__getinitargs__() usually # returns a copy of a dict/tuple that could possibly be reused # on another object's __getstate__. Caching these prevents # some wierd problems with the `id` of the object. self._misc_cache = [] def _flush_traits(self, obj): """Checks if the object has traits and ensures that the traits are set in the `__dict__` so we can pickle it. """ # Not needed with Traits3. return def _do(self, obj): obj_type = type(obj) key = self._get_id(obj) if key in self.obj_cache: return self._do_reference(obj) elif obj_type in self.type_map: return self.type_map[obj_type](obj) elif isinstance(obj, tuple): # Takes care of StateTuples. return self._do_tuple(obj) elif isinstance(obj, list): # Takes care of TraitListObjects. return self._do_list(obj) elif isinstance(obj, dict): # Takes care of TraitDictObjects. return self._do_dict(obj) elif hasattr(obj, '__dict__'): return self._do_instance(obj) def _get_id(self, value): try: key = hash(value) except TypeError: key = id(value) return key def _register(self, value): key = self._get_id(value) cache = self.obj_cache idx = len(cache) cache[key] = idx return idx def _do_basic_type(self, value): return value def _do_reference(self, value): key = self._get_id(value) idx = self.obj_cache[key] return dict(type='reference', id=idx, data=None) def _do_instance(self, value): # Flush out the traits. self._flush_traits(value) # Setup the relative paths of FilePaths before dumping. if self.file_name and isinstance(value, FilePath): value.set_relative(self.file_name) # Get the initargs. args = () if hasattr(value, '__getinitargs__') and value.__getinitargs__: args = value.__getinitargs__() # Get the object state. if hasattr(value, '__get_pure_state__'): state = value.__get_pure_state__() elif hasattr(value, '__getstate__'): state = value.__getstate__() else: state = value.__dict__ state.pop('__traits_version__', None) # Cache the args and state since they are likely to be gc'd. self._misc_cache.extend([args, state]) # Register and process. idx = self._register(value) args_data = self._do(args) data = self._do(state) # Get the version of the object. version = version_registry.get_version(value) module = value.__class__.__module__ class_name = value.__class__.__name__ return dict(type='instance', module=module, class_name=class_name, version=version, id=idx, initargs=args_data, data=data) def _do_state(self, value): metadata = value.__metadata__ args = metadata.get('initargs') state = dict(value) state.pop('__metadata__') self._misc_cache.extend([args, state]) idx = self._register(value) args_data = self._do(args) data = self._do(state) return dict(type='instance', module=metadata['module'], class_name=metadata['class_name'], version=metadata['version'], id=idx, initargs=args_data, data=data) def _do_tuple(self, value): idx = self._register(value) data = tuple([self._do(x) for x in value]) return dict(type='tuple', id=idx, data=data) def _do_list(self, value): idx = self._register(value) data = [self._do(x) for x in value] return dict(type='list', id=idx, data=data) def _do_dict(self, value): idx = self._register(value) vals = [self._do(x) for x in value.values()] data = dict(zip(value.keys(), vals)) return dict(type='dict', id=idx, data=data) def _do_numeric(self, value): idx = self._register(value) data = gzip_string(numpy.ndarray.dumps(value)).encode('base64') return dict(type='numeric', id=idx, data=data) ###################################################################### # `StateUnpickler` class ###################################################################### class StateUnpickler: """Unpickles the state of an object saved using StatePickler. Please note that unlike the standard Unpickler, no instances of any user class are created. The data for the state is obtained from the file or string, reference objects are setup to refer to the same state value and this state is returned in the form usually in the form of a dictionary. For example:: >>> class A: ... def __init__(self): ... self.attribute = 1 ... >>> a = A() >>> p = StatePickler() >>> s = p.dumps(a) >>> up = StateUnpickler() >>> state = up.loads_state(s) >>> state.__class__.__name__ 'State' >>> state.attribute 1 >>> state.__metadata__ {'class_name': 'A', 'has_instance': True, 'id': 0, 'initargs': (), 'module': '__main__', 'type': 'instance', 'version': [(('A', '__main__'), -1)]} Note that the state is actually a `State` instance and is navigable just like the original object. The details of the instance are stored in the `__metadata__` attribute. This is highly convenient since it is possible for someone to view and modify the state very easily. """ def __init__(self): self._clear() self.type_map = {'reference': self._do_reference, 'instance': self._do_instance, 'tuple': self._do_tuple, 'list': self._do_list, 'dict': self._do_dict, 'numeric': self._do_numeric, } def load_state(self, file): """Returns the state of an object loaded from the pickled data in the given file. """ try: self.file_name = file.name except AttributeError: pass data = cPickle.load(file) result = self._process(data) return result def loads_state(self, string): """Returns the state of an object loaded from the pickled data in the given string. """ data = cPickle.loads(string) result = self._process(data) return result ###################################################################### # Non-public methods ###################################################################### def _clear(self): # The file from which we are being loaded. self.file_name = '' # Cache of the objects. self._obj_cache = {} # Paths to the instances. self._instances = [] # Caches the references. self._refs = {} # Numeric arrays. self._numeric = {} def _set_has_instance(self, obj, value): if isinstance(obj, State): obj.__metadata__['has_instance'] = value elif isinstance(obj, (StateDict, StateList, StateTuple)): obj.has_instance = value def _process(self, data): result = self._do(data) # Setup all the Numeric arrays. Do this first since # references use this. for key, (path, val) in self._numeric.items(): exec('result%s = val'%path) # Setup the references so they really are references. for key, paths in self._refs.items(): for path in paths: x = self._obj_cache[key] exec('result%s = x'%path) # if the reference is to an instance append its path. if isinstance(x, State): self._instances.append(path) # Now setup the 'has_instance' attribute. If 'has_instance' # is True then the object contains an instance somewhere # inside it. for path in self._instances: pth = path while pth: exec('val = result%s'%pth) self._set_has_instance(val, True) end = pth.rfind('[') pth = pth[:end] # Now make sure that the first element also has_instance. self._set_has_instance(result, True) return result def _do(self, data, path=''): if type(data) == dict: return self.type_map[data['type']](data, path) else: return data def _do_reference(self, value, path): id = value['id'] if id in self._refs: self._refs[id].append(path) else: self._refs[id] = [path] return State(__metadata__=value) def _handle_file_path(self, value): if (value['class_name'] == 'FilePath') and \ ('file_path' in value['module']) and \ self.file_name: data = value['data']['data'] fp = FilePath(data['rel_pth']) fp.set_absolute(self.file_name) data['abs_pth'] = fp.abs_pth def _do_instance(self, value, path): self._instances.append(path) initargs = self._do(value['initargs'], path + '.__metadata__["initargs"]') # Handle FilePaths. self._handle_file_path(value) d = self._do(value['data'], path) md = dict(type='instance', module=value['module'], class_name=value['class_name'], version=value['version'], id=value['id'], initargs=initargs, has_instance=True) result = State(**d) result.__metadata__ = md self._obj_cache[value['id']] = result return result def _do_tuple(self, value, path): res = [] for i, x in enumerate(value['data']): res.append(self._do(x, path + '[%d]'%i)) result = StateTuple(res) self._obj_cache[value['id']] = result return result def _do_list(self, value, path): result = StateList() for i, x in enumerate(value['data']): result.append(self._do(x, path + '[%d]'%i)) self._obj_cache[value['id']] = result return result def _do_dict(self, value, path): result = StateDict() for key, val in value['data'].items(): result[key] = self._do(val, path + '["%s"]'%key) self._obj_cache[value['id']] = result return result def _do_numeric(self, value, path): junk = gunzip_string(value['data'].decode('base64')) result = numpy.loads(junk) self._numeric[value['id']] = (path, result) self._obj_cache[value['id']] = result return result ###################################################################### # `StateSetter` class ###################################################################### class StateSetter: """This is a convenience class that helps a user set the attributes of an object given its saved state. For instances it checks to see if a `__set_pure_state__` method exists and calls that when it sets the state. """ def __init__(self): # Stores the ids of instances already done. self._instance_ids = [] self.type_map = {State: self._do_instance, StateTuple: self._do_tuple, StateList: self._do_list, StateDict: self._do_dict, } def set(self, obj, state, ignore=None, first=None, last=None): """Sets the state of the object. This is to be used as a means to simplify loading the state of an object from its `__setstate__` method using the dictionary describing its state. Note that before the state is set, the registered handlers for the particular class are called in order to upgrade the version of the state to the latest version. Parameters ---------- - obj : `object` The object whose state is to be set. If this is `None` (default) then the object is created. - state : `dict` The dictionary representing the state of the object. - ignore : `list(str)` The list of attributes specified in this list are ignored and the state of these attributes are not set (this excludes the ones specified in `first` and `last`). If one specifies a '*' then all attributes are ignored except the ones specified in `first` and `last`. - first : `list(str)` The list of attributes specified in this list are set first (in order), before any other attributes are set. - last : `list(str)` The list of attributes specified in this list are set last (in order), after all other attributes are set. """ if (not isinstance(state, State)) and \ state.__metadata__['type'] != 'instance': raise StateSetterError, \ 'Can only set the attributes of an instance.' # Upgrade the state to the latest using the registry. self._update_and_check_state(obj, state) self._register(obj) # This wierdness is needed since the state's own `keys` might # be set to something else. state_keys = dict.keys(state) state_keys.remove('__metadata__') if first is None: first = [] if last is None: last = [] # Remove all the ignored keys. if ignore: if '*' in ignore: state_keys = first + last else: for name in ignore: try: state_keys.remove(name) except KeyError: pass # Do the `first` attributes. for key in first: state_keys.remove(key) self._do(obj, key, state[key]) # Remove the `last` attributes. for key in last: state_keys.remove(key) # Set the remaining attributes. for key in state_keys: self._do(obj, key, state[key]) # Do the last ones in order. for key in last: self._do(obj, key, state[key]) ###################################################################### # Non-public methods. ###################################################################### def _register(self, obj): idx = id(obj) if idx not in self._instance_ids: self._instance_ids.append(idx) def _is_registered(self, obj): return (id(obj) in self._instance_ids) def _has_instance(self, value): """Given something (`value`) that is part of the state this returns if the value has an instance embedded in it or not. """ if isinstance(value, State): return True elif isinstance(value, (StateDict, StateList, StateTuple)): return value.has_instance return False def _get_pure(self, value): """Returns the Python representation of the object (usually a list, tuple or dict) that has no instances embedded within it. """ result = value if self._has_instance(value): raise StateSetterError, \ 'Value has an instance: %s'%value if isinstance(value, (StateList, StateTuple)): result = [self._get_pure(x) for x in value] if isinstance(value, StateTuple): result = tuple(result) elif isinstance(value, StateDict): result = {} for k, v in value.items(): result[k] = self._get_pure(v) return result def _update_and_check_state(self, obj, state): """Updates the state from the registry and then checks if the object and state have same class. """ # Upgrade this state object to the latest using the registry. # This is done before testing because updating may change the # class name/module. version_registry.registry.update(state) # Make sure object and state have the same class and module names. metadata = state.__metadata__ cls = obj.__class__ if (metadata['class_name'] != cls.__name__): raise StateSetterError, \ 'Instance (%s) and state (%s) do not have the same class'\ ' name!'%(cls.__name__, metadata['class_name']) if (metadata['module'] != cls.__module__): raise StateSetterError, \ 'Instance (%s) and state (%s) do not have the same module'\ ' name!'%(cls.__module__, metadata['module']) def _do(self, obj, key, value): try: attr = getattr(obj, key) except AttributeError: raise StateSetterError, \ 'Object %s does not have an attribute called: %s'%(obj, key) if isinstance(value, (State, StateDict, StateList, StateTuple)): # Special handlers are needed. if not self._has_instance(value): result = self._get_pure(value) setattr(obj, key, result) elif isinstance(value, StateTuple): setattr(obj, key, self._do_tuple(getattr(obj, key), value)) else: self._do_object(getattr(obj, key), value) else: setattr(obj, key, value) def _do_object(self, obj, state): self.type_map[state.__class__](obj, state) def _do_instance(self, obj, state): if self._is_registered(obj): return else: self._register(obj) metadata = state.__metadata__ if hasattr(obj, '__set_pure_state__'): self._update_and_check_state(obj, state) obj.__set_pure_state__(state) elif 'tvtk_classes' in metadata['module']: self._update_and_check_state(obj, state) tmp = self._get_pure(StateDict(**state)) del tmp['__metadata__'] obj.__setstate__(tmp) else: # No need to update or check since `set` does it for us. self.set(obj, state) def _do_tuple(self, obj, state): if not self._has_instance(state): return self._get_pure(state) else: result = list(obj) self._do_list(result, state) return tuple(result) def _do_list(self, obj, state): if len(obj) == len(state): for i in range(len(obj)): if not self._has_instance(state[i]): obj[i] = self._get_pure(state[i]) elif isinstance(state[i], tuple): obj[i] = self._do_tuple(state[i]) else: self._do_object(obj[i], state[i]) else: raise StateSetterError,\ 'Cannot set state of list of incorrect size.' def _do_dict(self, obj, state): for key, value in state.items(): if not self._has_instance(value): obj[key] = self._get_pure(value) elif isinstance(value, tuple): obj[key] = self._do_tuple(value) else: self._do_object(obj[key], value) ###################################################################### # Internal Utility functions. ###################################################################### def _get_file_read(f): if hasattr(f, 'read'): return f elif isinstance(f, basestring): return open(f, 'rb') else: raise TypeError, 'Given object is neither a file or String' def _get_file_write(f): if hasattr(f, 'write'): return f elif isinstance(f, basestring): return open(f, 'wb') else: raise TypeError, 'Given object is neither a file or String' ###################################################################### # Utility functions. ###################################################################### def dump(value, file): """Pickles the state of the object (`value`) into the passed file (or file name). """ f = _get_file_write(file) try: StatePickler().dump(value, f) finally: f.flush() if f is not file: f.close() def dumps(value): """Pickles the state of the object (`value`) and returns a string. """ return StatePickler().dumps(value) def load_state(file): """Returns the state of an object loaded from the pickled data in the given file (or file name). """ f = _get_file_read(file) try: state = StateUnpickler().load_state(f) finally: if f is not file: f.close() return state def loads_state(string): """Returns the state of an object loaded from the pickled data in the given string. """ return StateUnpickler().loads_state(string) def get_state(obj): """Returns the state of the object (usually as a dictionary). The returned state may be used directy to set the state of the object via `set_state`. """ s = dumps(obj) return loads_state(s) def set_state(obj, state, ignore=None, first=None, last=None): StateSetter().set(obj, state, ignore, first, last) set_state.__doc__ = StateSetter.set.__doc__ def update_state(state): """Given the state of an object, this updates the state to the latest version using the handlers given in the version registry. The state is modified in-place. """ version_registry.registry.update(state) def create_instance(state): """Create an instance from the state if possible. """ if (not isinstance(state, State)) and \ ('class_name' not in state.__metadata__): raise StateSetterError, 'No class information in state' metadata = state.__metadata__ class_name = metadata.get('class_name') mod_name = metadata.get('module') if 'tvtk_classes' in mod_name: # FIXME: This sort of special-case is probably indicative of something # that needs more thought, plus it makes it tought to decide whether # this component depends on tvtk! from tvtk.api import tvtk return getattr(tvtk, class_name)() initargs = metadata['initargs'] if initargs.has_instance: raise StateUnpicklerError, \ 'Cannot unpickle non-trivial initargs' __import__(mod_name, globals(), locals(), class_name) mod = sys.modules[mod_name] cls = getattr(mod, class_name) return cls(*initargs) apptools-4.1.0/apptools/persistence/__init__.py0000644000175100001440000000060011674464005022637 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2004 by Enthought, Inc. # All rights reserved. #------------------------------------------------------------------------------ """ Supports flexible pickling and unpickling of the state of a Python object to a dictionary. Part of the AppTools project of the Enthought Tool Suite. """ apptools-4.1.0/apptools/appscripting/0000755000175100001440000000000011674464005020711 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/appscripting/scriptable_type.py0000644000175100001440000001216611674464005024462 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Standard library imports. import inspect import types # Enthought library imports. from traits.api import HasTraits # Local imports. from package_globals import get_script_manager from scriptable import scriptable, Scriptable def create_scriptable_type(scripted_type, name=None, bind_policy='auto', api=None, includes=None, excludes=None, script_init=True): """Create and return a new type based on the given scripted_type that will (by default) have its public methods and traits (ie. those not beginning with an underscore) made scriptable. name is the name that objects of this type will be bound to. It defaults to the name of scripted_type with the first character forced to lower case. It is ignored if script_init is False. bind_policy determines what happens if a name is already bound. If the policy is 'auto' then a numerical suffix will be added to the name, if necessary, to make it unique. If the policy is 'unique' then an exception is raised. If the policy is 'rebind' then the previous binding is discarded. It is ignored if script_init is False. The default is 'auto'. If api is given then it is a class, or a list of classes, that define the attributes that will be made scriptable. Otherwise if includes is given it is a list of names of attributes that will be made scriptable. Otherwise all the public attributes of scripted_type will be made scriptable except those in the excludes list. Irrespective of any other arguments, if script_init is set then the __init__() method will always be made scriptable. """ def __init__(self, *args, **kwargs): """Initialise the dynamic sub-class instance.""" get_script_manager().new_object(self, scripted_type, args, kwargs, name, bind_policy) scripted_type.__init__(self, *args, **kwargs) # See if we need to pull all attribute names from a type. if api is not None: if isinstance(api, list): src = api else: src = [api] elif includes is None: src = [scripted_type] else: names = includes src = None if src: ndict = {} for cls in src: if issubclass(cls, HasTraits): for n in cls.class_traits().keys(): if not n.startswith('_') and not n.startswith('trait'): ndict[n] = None for c in inspect.getmro(cls): if c is HasTraits: break for n in c.__dict__.keys(): if not n.startswith('_'): ndict[n] = None # Respect the excludes so long as there was no explicit API. if api is None and excludes is not None: for n in excludes: try: del ndict[n] except KeyError: pass names = ndict.keys() # Create the type dictionary containing replacements for everything that # needs to be scriptable. type_dict = {} if script_init: type_dict['__init__'] = __init__ if issubclass(scripted_type, HasTraits): traits = scripted_type.class_traits() for n in names: trait = traits.get(n) if trait is not None: type_dict[n] = Scriptable(trait) for n in names: try: attr = getattr(scripted_type, n) except AttributeError: continue if type(attr) is types.MethodType: type_dict[n] = scriptable(attr) type_name = 'Scriptable(%s)' % scripted_type.__name__ return type(type_name, (scripted_type, ), type_dict) def make_object_scriptable(obj, api=None, includes=None, excludes=None): """Make (by default) an object's public methods and traits (ie. those not beginning with an underscore) scriptable. If api is given then it is a class, or a list of classes, that define the attributes that will be made scriptable. Otherwise if includes is given it is a list of names of attributes that will be made scriptable. Otherwise all the public attributes of scripted_type will be made scriptable except those in the excludes list. """ # Create the new scriptable type. new_type = create_scriptable_type(obj.__class__, api=api, includes=includes, excludes=excludes, script_init=False) # Fix the object's type to make it scriptable. obj.__class__ = new_type apptools-4.1.0/apptools/appscripting/api.py0000644000175100001440000000173511674464005022042 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ from scriptable_type import create_scriptable_type, make_object_scriptable from i_bind_event import IBindEvent from i_script_manager import IScriptManager from package_globals import get_script_manager, set_script_manager from script_manager import ScriptManager from scriptable import scriptable, Scriptable apptools-4.1.0/apptools/appscripting/i_bind_event.py0000644000175100001440000000227011674464005023711 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import Any, Interface, Str class IBindEvent(Interface): """The bind event interface. A corresponding instance is the value of the event fired when a scriptable object is bound or unbound to or from a name. """ #### 'IBindEvent' interface ############################################### # This is the name being bound or unbound. name = Str # This is the object being bound to the name. It is None if the name is # being unbound. obj = Any apptools-4.1.0/apptools/appscripting/i_script_manager.py0000644000175100001440000001063511674464005024576 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import Bool, Event, Instance, Interface, Unicode # Local imports. from i_bind_event import IBindEvent class IScriptManager(Interface): """ The script manager interface. A script manager is responsible for the recording of appropriately annotated user actions as scripts that can be executed without user intervention at a later time. Typically an application would have a single script manager. """ #### 'IScriptManager' interface ########################################### # This event is fired whenever a scriptable object is bound or unbound. It # is intended to be used by an interactive Python shell to give the # advanced user access to the scriptable objects. If an object is created # via a factory then the event is fired when the factory is called, and not # when the factory is bound. bind_event = Event(IBindEvent) # This is set if user actions are being recorded as a script. It is # maintained by the script manager. recording = Bool(False) # This is the text of the script currently being recorded (or the last # recorded script if none is currently being recorded). It is updated # automatically as the user performs actions. script = Unicode # This event is fired when the recorded script changes. The value of the # event will be the ScriptManager instance. script_updated = Event(Instance('apptools.appscripting.api.IScriptManager')) ########################################################################### # 'IScriptManager' interface. ########################################################################### def bind(self, obj, name=None, bind_policy='unique', api=None, includes=None, excludes=None): """ Bind obj to name and make (by default) its public methods and traits (ie. those not beginning with an underscore) scriptable. The default value of name is the type of obj with the first character forced to lower case. bind_policy determines what happens if the name is already bound. If the policy is 'auto' then a numerical suffix will be added to the name, if necessary, to make it unique. If the policy is 'unique' then an exception is raised. If the policy is 'rebind' then the previous binding is discarded. The default is 'unique' If api is given then it is a class, or a list of classes, that define the attributes that will be made scriptable. Otherwise if includes is given it is a list of names of attributes that will be made scriptable. Otherwise all the public attributes of scripted_type will be made scriptable except those in the excludes list. """ def bind_factory(self, factory, name, bind_policy='unique', api=None, includes=None, excludes=None): """ Bind factory to name. This does the same as the bind() method except that it uses a factory that will be called later on to create the object only if the object is needed. See the documentation for bind() for a description of the remaining arguments. """ def run(self, script): """ Run the given script, either a string or a file-like object. """ def run_file(self, file_name): """ Run the given script file. """ def start_recording(self): """ Start the recording of user actions. The 'script' trait is cleared and all subsequent actions are added to 'script'. The 'recording' trait is updated appropriately. """ def stop_recording(self): """ Stop the recording of user actions. The 'recording' trait is updated appropriately. """ apptools-4.1.0/apptools/appscripting/scriptable.py0000644000175100001440000000713711674464005023423 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import Any, Property, Undefined from traits.traits import trait_cast # Local imports. from package_globals import get_script_manager # This is the guard that ensures that only outermost scriptable methods get # recorded. _outermost_call = True def scriptable(func): """ This is the decorator applied to functions and methods to mark them as being scriptable. """ def _scripter(*args, **kwargs): """ This is the wrapper that is returned in place of the scriptable method. """ global _outermost_call if _outermost_call: _outermost_call = False # See if there is an script manager set. sm = get_script_manager() if func.func_name == '__init__': sm.new_object(args[0], type(args[0]), args[1:], kwargs) try: result = func(*args, **kwargs) finally: _outermost_call = True else: # Record the ordinary method. try: result = sm.record_method(func, args, kwargs) finally: _outermost_call = True else: # We aren't at the outermost call so just invoke the method. result = func(*args, **kwargs) return result # Be a good citizen. _scripter.__name__ = func.__name__ _scripter.__doc__ = func.__doc__ _scripter.__dict__.update(func.__dict__) return _scripter def _scriptable_get(obj, name): """ The getter for a scriptable trait. """ global _outermost_call saved_outermost = _outermost_call _outermost_call = False try: result = getattr(obj, '_' + name, None) if result is None: result = obj.trait(name).default finally: _outermost_call = saved_outermost if saved_outermost: get_script_manager().record_trait_get(obj, name, result) return result def _scriptable_set(obj, name, value): """ The setter for a scriptable trait. """ if _outermost_call: get_script_manager().record_trait_set(obj, name, value) _name = '_' + name old_value = getattr(obj, _name, Undefined) if old_value is not value: setattr(obj, _name, value) obj.trait_property_changed(name, old_value, value) def Scriptable(trait=Any, **metadata): """ Scriptable is a wrapper around another trait that makes it scriptable, ie. changes to its value can be recorded. If a trait is read, but the value isn't set to another scriptable trait or passed to a scriptable method then the read will not be included in the recorded script. To make sure a read is always recorded set the 'has_side_effects' argument to True. """ trait = trait_cast(trait) metadata['default'] = trait.default_value()[1] return Property(_scriptable_get, _scriptable_set, trait=trait, **metadata) apptools-4.1.0/apptools/appscripting/bind_event.py0000644000175100001440000000226011674464005023400 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import Any, HasTraits, implements, Str # Local imports. from i_bind_event import IBindEvent class BindEvent(HasTraits): """The default implementation of the bind event interface.""" implements(IBindEvent) #### 'IBindEvent' interface ############################################### # This is the name being bound or unbound. name = Str # This is the object being bound to the name. It is None if the name is # being unbound. obj = Any apptools-4.1.0/apptools/appscripting/action/0000755000175100001440000000000011674464005022166 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/appscripting/action/api.py0000644000175100001440000000142611674464005023314 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ from start_recording_action import StartRecordingAction from stop_recording_action import StopRecordingAction apptools-4.1.0/apptools/appscripting/action/start_recording_action.py0000644000175100001440000000261511674464005027272 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from pyface.action.api import Action from traits.api import Unicode # Local imports. from apptools.appscripting.package_globals import get_script_manager class StartRecordingAction(Action): """An action that starts the recording of changes to scriptable objects to a script.""" #### 'Action' interface ################################################### name = Unicode("Start recording") ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """ Perform the action. """ get_script_manager().start_recording() apptools-4.1.0/apptools/appscripting/action/__init__.py0000644000175100001440000000064711674464005024306 0ustar ischnellusers00000000000000# Copyright (c) 2005-2011, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. apptools-4.1.0/apptools/appscripting/action/stop_recording_action.py0000644000175100001440000000417711674464005027127 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from pyface.action.api import Action from traits.api import Bool, Unicode # Local imports. from apptools.appscripting.package_globals import get_script_manager class StopRecordingAction(Action): """An action that stops the recording of changes to scriptable objects to a script.""" #### 'Action' interface ################################################### enabled = Bool(False) name = Unicode("Stop recording") ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **traits): """ Initialise the instance. """ super(StopRecordingAction, self).__init__(**traits) get_script_manager().on_trait_change(self._on_recording, 'recording') ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """ Perform the action. """ get_script_manager().stop_recording() ########################################################################### # Private interface. ########################################################################### def _on_recording(self, new): """ Handle a change to the script manager's recording trait. """ self.enabled = new apptools-4.1.0/apptools/appscripting/lazy_namespace.py0000644000175100001440000000722711674464005024266 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import Any, Callable, HasTraits # Local imports. from bind_event import BindEvent from package_globals import get_script_manager from scriptable_type import make_object_scriptable class FactoryWrapper(HasTraits): """The FactoryWrapper class wraps a factory for an object.""" #### 'FactoryWrapper' interface ########################################### # The optional object that defines the scripting API. api = Any # The object factory. factory = Callable # The optional attribute include list. includes = Any # The optional attribute exclude list. excludes = Any ########################################################################### # 'FactoryWrapper' interface. ########################################################################### def create_scriptable_object(self, name): """Invoke the factory to create the object then make it scriptable.""" obj = self.factory() sm = get_script_manager() sm.bind_event = BindEvent(name=name, obj=obj) make_object_scriptable(obj, self.api, self.includes, self.excludes) return obj class _LazyNode(object): """The _LazyNode class implements a node in a lazy namespace that will automatically invoke a factory if one is referenced.""" def __getattribute__(self, name): value = super(_LazyNode, self).__getattribute__(name) if isinstance(value, FactoryWrapper): value = value.create_scriptable_object(name) setattr(self, name, value) return value class LazyNamespace(dict): """The LazyNamespace class implements a lazy namespace that will automatically invoke a factory if one is referenced.""" def __getitem__(self, name): value = super(LazyNamespace, self).__getitem__(name) if isinstance(value, FactoryWrapper): value = value.create_scriptable_object(name) self[name] = value return value def add_to_namespace(so, name, nspace): """Add a named scriptable object (or a factory for one) to a lazy namespace. If a name is a dotted name then intermediary nodes in the namespace are created as required.""" def save_obj(obj, name): if isinstance(nspace, LazyNamespace): nspace[name] = obj else: setattr(nspace, name, obj) parts = name.split('.') for part in parts[:-1]: if isinstance(nspace, LazyNamespace): try: next_nspace = nspace[part] except KeyError: next_nspace = _LazyNode() elif isinstance(nspace, _LazyNode): try: next_nspace = nspace[part] except KeyError: next_nspace = _LazyNode() else: raise NameError save_obj(next_nspace, part) nspace = next_nspace if not isinstance(nspace, (LazyNamespace, _LazyNode)): raise NameError save_obj(so, parts[-1]) apptools-4.1.0/apptools/appscripting/script_manager.py0000644000175100001440000006412711674464005024273 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Standard library imports. import datetime import types import weakref # Enthought library imports. from traits.api import Any, Bool, Callable, Dict, Event, HasTraits, \ implements, Instance, Int, List, Property, Str, Unicode # Local imports. from bind_event import BindEvent from i_bind_event import IBindEvent from i_script_manager import IScriptManager from lazy_namespace import add_to_namespace, FactoryWrapper, LazyNamespace from scriptable_type import make_object_scriptable class _BoundObject(HasTraits): """The base class for any object that can be bound to a name.""" #### '_BoundObject' interface ############################################# # Set if the object was explicitly bound. explicitly_bound = Bool(True) # The name the object is bound to. name = Str # The object being bound. obj = Any class _ScriptObject(_BoundObject): """The _ScriptObject class encapsulates a scriptable object.""" #### '_BoundObject' interface ############################################# # The object being bound. obj = Property #### '_ScriptObject' interface ############################################ # The positional arguments passed to __init__ after being converted to # strings. A particular argument may be an exception if it couldn't be # converted. args = List # The keyword arguments passed to __init__ after being converted to # strings. A particular argument may be an exception if it couldn't be # converted. kwargs = Dict # The id of the object. obj_id = Int # A weak reference to the object. obj_ref = Any # The type of the scriptable object. scripted_type = Any ########################################################################### # Private interface. ########################################################################### def _get_obj(self): """The property getter.""" return self.obj_ref() class _ScriptCall(HasTraits): """ The _ScriptCall class is the base class for all script calls. """ #### '_ScriptCall' interface ############################################## # The name of the call. name = Str # The scriptable object. so = Any ########################################################################### # '_ScriptCall' interface. ########################################################################### def as_str(self, sm, so_needed): """ Return the string equivalent of the call, updated the list of needed scriptable objects if required. """ raise NotImplementedError class _ScriptTraitGet(_ScriptCall): """ The _ScriptTraitGet class encapsulates a single call to the get of a scriptable trait. """ #### '_ScriptTraitGet' interface ########################################## # Set if the getter has side effects. has_side_effects = Bool(False) # The result of the get. Keeping a reference to it means that the memory # can't get reused. result = Any ########################################################################### # '_ScriptCall' interface. ########################################################################### def as_str(self, sm, so_needed): """ Return the string equivalent of the call, updated the list of needed scriptable objects if required. """ # Ignore if it is no longer bound. if not self.so.name: return None if self.result is None: rstr = "" else: nr, _ = sm._results[id(self.result)] if nr >= 0: rstr = "r%d = " % nr else: rstr = "" # Unless getter has side effects, if the result is not needed then # don't bother including it in the script. if not self.has_side_effects and rstr == "": return None so = sm.arg_as_string(self.so, so_needed) return "%s%s.%s" % (rstr, so, self.name) class _ScriptTraitSet(_ScriptCall): """ The _ScriptTraitSet class encapsulates a single call to the set of a scriptable trait. """ #### '_ScriptTraitSet' interface ########################################## # The value the trait is set to. value = Any ########################################################################### # '_ScriptCall' interface. ########################################################################### def as_str(self, sm, so_needed): """ Return the string equivalent of the call, updated the list of needed scriptable objects if required. """ # Ignore if it is no longer bound. if not self.so.name: return None so = sm.arg_as_string(self.so, so_needed) value = sm.arg_as_string(self.value, so_needed) return "%s.%s = %s" % (so, self.name, value) class _ScriptMethod(_ScriptCall): """ The _ScriptMethod class encapsulates a single call to a scriptable method. """ #### '_ScriptMethod' interface ############################################ # The positional arguments passed to the method after being converted to # strings. A particular argument may be an exception if it couldn't be # converted. args = List # The keyword arguments passed to the method after being converted to # strings. A particular argument may be an exception if it couldn't be # converted. kwargs = Dict # The result of the method call. Keeping a reference to it means that the # memory can't get reused. result = Any ########################################################################### # '_ScriptCall' interface. ########################################################################### def as_str(self, sm, so_needed): """ Return the string equivalent of the call, updated the list of needed scriptable objects if required. """ # Ignore if it is no longer bound. if self.so and not self.so.name: return None if self.result is None: rstr = "" elif type(self.result) is type(()): rlist = [] needed = False for r in self.result: nr, _ = sm._results[id(r)] if nr >= 0: rlist.append("r%d" % nr) needed = True else: rlist.append("_") if needed: rstr = ", ".join(rlist) + " = " else: rstr = "" else: nr, _ = sm._results[id(self.result)] if nr >= 0: rstr = "r%d = " % nr else: rstr = "" if self.so: so = sm.arg_as_string(self.so, so_needed) + '.' else: so = '' args = sm.args_as_string_list(self.args, self.kwargs, so_needed) return "%s%s%s(%s)" % (rstr, so, self.name, ", ".join(args)) class _FactoryObject(_BoundObject): """ The _FactoryObject class wraps a factory that lazily creates scriptable objects. """ #### '_BoundObject' interface ############################################# # The object being bound. obj = Property #### '_FactoryObject' interface ########################################### # The optional object that defines the scripting API. api = Any # The scriptable object factory. factory = Callable # The optional attribute include list. includes = Any # The optional attribute exclude list. excludes = Any ########################################################################### # Private interface. ########################################################################### def _get_obj(self): """The property getter.""" return FactoryWrapper(factory=self.factory, api=self.api, includes=self.includes, excludes=self.excludes) class ScriptManager(HasTraits): """ The ScriptManager class is the default implementation of IScriptManager. """ implements(IScriptManager) #### 'IScriptManager' interface ########################################### # This event is fired whenever a scriptable object is bound or unbound. It # is intended to be used by an interactive Python shell to give the # advanced user access to the scriptable objects. If an object is created # via a factory then the event is fired when the factory is called, and not # when the factory is bound. bind_event = Event(IBindEvent) # This is set if user actions are being recorded as a script. It is # maintained by the script manager. recording = Bool(False) # This is the text of the script currently being recorded (or the last # recorded script if none is currently being recorded). It is updated # automatically as the user performs actions. script = Property(Unicode) # This event is fired when the recorded script changes. The value of the # event will be the ScriptManager instance. script_updated = Event(IScriptManager) #### Private interface #################################################### # The list of calls to scriptable calls. _calls = List(Instance(_ScriptCall)) # The dictionary of bound names. The value is the next numerical suffix # to use when the binding policy is 'auto'. _names = Dict # The dictionary of _BoundObject instances keyed by the name the object is # bound to. _namespace = Dict # The next sequential result number. _next_result_nr = Int # The results returned by previous scriptable calls. The key is the id() # of the result object. The value is a two element tuple of the sequential # result number (easier for the user to use than the id()) and the result # object itself. _results = Dict # The dictionary of _ScriptObject instances keyed by the object's id(). _so_by_id = Dict # The dictionary of _ScriptObject instances keyed by the a weak reference # to the object. _so_by_ref = Dict # The date and time when the script was recorded. _when_started = Any ########################################################################### # 'IScriptManager' interface. ########################################################################### def bind(self, obj, name=None, bind_policy='unique', api=None, includes=None, excludes=None): """ Bind obj to name and make (by default) its public methods and traits (ie. those not beginning with an underscore) scriptable. The default value of name is the type of obj with the first character forced to lower case. name may be a dotted name (eg. 'abc.def.xyz'). bind_policy determines what happens if the name is already bound. If the policy is 'auto' then a numerical suffix will be added to the name, if necessary, to make it unique. If the policy is 'unique' then an exception is raised. If the policy is 'rebind' then the previous binding is discarded. The default is 'unique' If api is given then it is a class, or a list of classes, that define the attributes that will be made scriptable. Otherwise if includes is given it is a list of names of attributes that will be made scriptable. Otherwise all the public attributes of scripted_type will be made scriptable except those in the excludes list. """ # Register the object. self.new_object(obj, obj.__class__, name=name, bind_policy=bind_policy) # Make it scriptable. make_object_scriptable(obj, api=api, includes=includes, excludes=excludes) def bind_factory(self, factory, name, bind_policy='unique', api=None, includes=None, excludes=None): """ Bind factory to name. This does the same as the bind() method except that it uses a factory that will be called later on to create the object only if the object is needed. See the documentation for bind() for a description of the remaining arguments. """ name = self._unique_name(name, bind_policy) self._namespace[name] = _FactoryObject(name=name, factory=factory, api=api, includes=includes, excludes=excludes) def run(self, script): """ Run the given script, either a string or a file-like object. """ # Initialise the namespace with all explicitly bound objects. nspace = LazyNamespace() for name, bo in self._namespace.iteritems(): if bo.explicitly_bound: add_to_namespace(bo.obj, name, nspace) exec script in nspace def run_file(self, file_name): """ Run the given script file. """ f = open(file_name) self.run(f) f.close() def start_recording(self): """ Start the recording of user actions. The 'script' trait is cleared and all subsequent actions are added to 'script'. The 'recording' trait is updated appropriately. """ self._calls = [] self._next_result_nr = 0 self._results = {} self.recording = True self.script_updated = self def stop_recording(self): """ Stop the recording of user actions. The 'recording' trait is updated appropriately. """ self.recording = False ########################################################################### # 'ScriptManager' interface. ########################################################################### def record_method(self, func, args, kwargs): """ Record the call of a method of a ScriptableObject instance and return the result. This is intended to be used only by the scriptable decorator. """ if self.recording: # Record the arguments before the function has a chance to modify # them. srec = self._new_method(func, args, kwargs) result = func(*args, **kwargs) self._add_method(srec, result) self.script_updated = self else: result = func(*args, **kwargs) return result def record_trait_get(self, obj, name, result): """ Record the get of a trait of a scriptable object. This is intended to be used only by the Scriptable trait getter. """ if self.recording: side_effects = self._add_trait_get(obj, name, result) # Don't needlessly fire the event if there are no side effects. if side_effects: self.script_updated = self def record_trait_set(self, obj, name, value): """ Record the set of a trait of a scriptable object. This is intended to be used only by the Scriptable trait getter. """ if self.recording: self._add_trait_set(obj, name, value) self.script_updated = self def new_object(self, obj, scripted_type, args=None, kwargs=None, name=None, bind_policy='auto'): """ Register a scriptable object and the arguments used to create it. If no arguments were provided then assume the object is being explicitly bound. """ # The name defaults to the type name. if not name: name = scripted_type.__name__ name = name[0].lower() + name[1:] name = self._unique_name(name, bind_policy) obj_id = id(obj) obj_ref = weakref.ref(obj, self._gc_script_obj) so = _ScriptObject(name=name, obj_id=obj_id, obj_ref=obj_ref, scripted_type=scripted_type) # If we are told how to create the object then it must be implicitly # bound. if args is not None: # Convert each argument to its string representation if possible. # Doing this now avoids problems with mutable arguments. so.args = [self._scriptable_object_as_string(a) for a in args] for n, value in kwargs.iteritems(): so.kwargs[n] = self._scriptable_object_as_string(value) so.explicitly_bound = False # Remember the scriptable object via the different access methods. self._so_by_id[obj_id] = so self._so_by_ref[obj_ref] = so self._namespace[name] = so # Note that if anything listening to this event doesn't use weak # references then the object will be kept alive. self.bind_event = BindEvent(name=name, obj=obj) @staticmethod def args_as_string_list(args, kwargs, so_needed=None): """ Return a complete argument list from sets of positional and keyword arguments. Update the optional so_needed list for those arguments that refer to a scriptable object. """ if so_needed is None: so_needed = [] all_args = [] for arg in args: s = ScriptManager.arg_as_string(arg, so_needed) all_args.append(s) for name, value in kwargs.iteritems(): s = ScriptManager.arg_as_string(value, so_needed) all_args.append('%s=%s' % (name, s)) return all_args @staticmethod def arg_as_string(arg, so_needed): """ Return the string representation of an argument. Update the so_needed list if the argument refers to a scriptable object. Any delayed conversion exception is handled here. """ if isinstance(arg, Exception): raise arg if isinstance(arg, _ScriptObject): # Check it hasn't been unbound. if not arg.name: raise NameError("%s has been unbound but is needed by the script" % arg.obj_ref()) # Add it to the needed list if it isn't already there. if arg not in so_needed: so_needed.append(arg) arg = arg.name return arg ########################################################################### # Private interface. ########################################################################### def _new_method(self, func, args, kwargs): """ Return an object that encapsulates a call to a scriptable method. _add_method() must be called to add it to the current script. """ # Convert each argument to its string representation if possible. # Doing this now avoids problems with mutable arguments. nargs = [self._object_as_string(arg) for arg in args] if type(func) is types.FunctionType: so = None else: so = nargs[0] nargs = nargs[1:] nkwargs = {} for name, value in kwargs.iteritems(): nkwargs[name] = self._object_as_string(value) return _ScriptMethod(name=func.func_name, so=so, args=nargs, kwargs=nkwargs) def _add_method(self, entry, result): """ Add a method call (returned by _new_method()), with it's associated result and ID, to the current script. """ self._start_script() if result is not None: # Assume that a tuple represents multiple returned values - not # necessarily a valid assumption unless we make it a rule for # scriptable functions. if type(result) is type(()): for r in result: self._save_result(r) else: self._save_result(result) entry.result = result self._calls.append(entry) def _add_trait_get(self, obj, name, result): """ Add a call to a trait getter, with it's associated result and ID, to the current script. Return True if the get had side effects. """ self._start_script() side_effects = obj.trait(name).has_side_effects if side_effects is None: side_effects = False so = self._object_as_string(obj) if result is not None: self._save_result(result) self._calls.append(_ScriptTraitGet(so=so, name=name, result=result, has_side_effects=side_effects)) return side_effects def _add_trait_set(self, obj, name, value): """ Add a call to a trait setter, with it's associated value and ID, to the current script. """ self._start_script() so = self._object_as_string(obj) value = self._object_as_string(value) self._calls.append(_ScriptTraitSet(so=so, name=name, value=value)) def _unique_name(self, name, bind_policy): """ Return a name that is guaranteed to be unique according to the bind policy. """ # See if the name is already is use. bo = self._namespace.get(name) if bo is None: self._names[name] = 1 elif bind_policy == 'auto': suff = self._names[name] self._names[name] = suff + 1 name = '%s%d' % (name, suff) elif bind_policy == 'rebind': self._unbind(bo) else: raise NameError("\"%s\" is already bound to a scriptable object" % name) return name def _unbind(self, bo): """Unbind the given bound object.""" # Tell everybody it is no longer bound. Don't bother if it is a # factory because the corresponding bound event wouldn't have been # fired. if not isinstance(bo, _FactoryObject): self.bind_event = BindEvent(name=bo.name, obj=None) # Forget about it. del self._namespace[bo.name] bo.name = '' @staticmethod def _gc_script_obj(obj_ref): """ The callback invoked when a scriptable object is garbage collected. """ # Avoid recursive imports. from package_globals import get_script_manager sm = get_script_manager() so = sm._so_by_ref[obj_ref] if so.name: sm._unbind(so) del sm._so_by_id[so.obj_id] del sm._so_by_ref[so.obj_ref] def _start_script(self): """ Save when a script recording is started. """ if len(self._calls) == 0: self._when_started = datetime.datetime.now().strftime('%c') def _object_as_string(self, obj): """ Convert an object to a string as it will appear in a script. An exception may be returned (not raised) if there was an error in the conversion. """ obj_id = id(obj) # See if the argument is the result of a previous call. nr, _ = self._results.get(obj_id, (None, None)) if nr is not None: if nr < 0: nr = self._next_result_nr self._next_result_nr += 1 # Key on the ID of the argument (which is hashable) rather than # the argument itself (which might not be). self._results[obj_id] = (nr, obj) return "r%d" % nr return self._scriptable_object_as_string(obj) def _scriptable_object_as_string(self, obj): """ Convert an object to a string as it will appear in a script. An exception may be returned (not raised) if there was an error in the conversion. """ obj_id = id(obj) # If it is a scriptable object we return the object and convert it to a # string later when we know it is really needed. so = self._so_by_id.get(obj_id) if so is not None: return so # Use the repr result if it doesn't appear to be the generic response, # ie. it doesn't contain its own address as a hex string. s = repr(obj) if hex(obj_id) not in s: return s # We don't know how to represent the argument as a string. This is # most likely because an appropriate __init__ hasn't been made # scriptable. We don't raise an exception until the user decides to # convert the calls to a script. return ValueError("unable to create a script representation of %s" % obj) def _save_result(self, result): """ Save the result of a call to a scriptable method so that it can be recognised later. """ if id(result) not in self._results: self._results[id(result)] = (-1, result) def _get_script(self): """ Convert the current list of calls to a script. """ # Handle the trivial case. if len(self._calls) == 0: return "" # Generate the header. header = "# Script generated %s" % self._when_started # Generate the calls. so_needed = [] calls = [] for call in self._calls: s = call.as_str(self, so_needed) if s: calls.append(s) calls = "\n".join(calls) # Generate the scriptable object constructors. types_needed = [] ctors = [] for so in so_needed: if so.explicitly_bound: continue so_type = so.scripted_type args = self.args_as_string_list(so.args, so.kwargs) ctors.append("%s = %s(%s)" % (so.name, so_type.__name__, ", ".join(args))) # See if a new import is needed. if so_type not in types_needed: types_needed.append(so_type) ctors = "\n".join(ctors) # Generate the import statements. imports = [] for so_type in types_needed: imports.append("from %s import %s" % (so_type.__module__, so_type.__name__)) imports = "\n".join(imports) return "\n\n".join([header, imports, ctors, calls]) + "\n" apptools-4.1.0/apptools/appscripting/__init__.py0000644000175100001440000000101611674464005023020 0ustar ischnellusers00000000000000# Copyright (c) 2005-2011, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. """ Application scripting framework, part of the AppTools project of the Enthought Tool Suite. """ apptools-4.1.0/apptools/appscripting/package_globals.py0000644000175100001440000000231611674464005024363 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # The script manager. _script_manager = None def get_script_manager(): """Return the IScriptManager implementation, creating a ScriptManager instance if no other implementation has been set.""" global _script_manager if _script_manager is None: from script_manager import ScriptManager _script_manager = ScriptManager() return _script_manager def set_script_manager(script_manager): """Set the IScriptManager implementation to use.""" global _script_manager _script_manager = script_manager apptools-4.1.0/apptools/__init__.py0000644000175100001440000000022011755026000020276 0ustar ischnellusers00000000000000# Copyright (c) 2007-2012 by Enthought, Inc. # All rights reserved. __version__ = '4.1.0' __requires__ = [ 'traitsui', 'configobj', ] apptools-4.1.0/apptools/naming/0000755000175100001440000000000011674464005017457 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/naming/py_object_factory.py0000644000175100001440000000342411674464005023541 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Object factory for Python namespace contexts. """ # Local imports. from object_factory import ObjectFactory from reference import Reference class PyObjectFactory(ObjectFactory): """ Object factory for Python namespace contexts. """ ########################################################################### # 'ObjectFactory' interface. ########################################################################### def get_object_instance(self, state, name, context): """ Creates an object using the specified state information. """ obj = None if isinstance(state, Reference): if len(state.addresses) > 0: if state.addresses[0].type == 'py_context': namespace = state.addresses[0].content obj = context._context_factory(name, namespace) elif hasattr(state, '__dict__'): from apptools.naming.py_context import PyContext if not isinstance(state, PyContext): obj = context._context_factory(name, state) return obj ### EOF ####################################################################### apptools-4.1.0/apptools/naming/reference.py0000644000175100001440000000357111674464005021775 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A reference to an object that lives outside of the naming system. """ # Enthought library imports. from traits.api import Any, HasPrivateTraits, List, Str # Local imports. from address import Address class Reference(HasPrivateTraits): """ A reference to an object that lives outside of the naming system. References provide a way to store the address(s) of objects that live outside of the naming system. A reference consists of a list of addresses that represent a communications endpoint for the object being referenced. A reference also contains information to assist in the creation of an instance of the object to which it refers. It contains the name of the class that will be created and the class name and location of a factory that will be used to do the actual instance creation. """ #### 'Reference' interface ################################################ # The list of addresses that can be used to 'contact' the object. addresses = List(Address) # The class name of the object that this reference refers to. class_name = Str # The class name of the object factory. factory_class_name = Str #### EOF ###################################################################### apptools-4.1.0/apptools/naming/tests/0000755000175100001440000000000011674464005020621 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/naming/tests/pyfs_context_test_case.py0000644000175100001440000002756311674464005025767 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Tests naming operations on PyFS contexts. """ # Standard library imports. import os, shutil, unittest # Enthought library imports. from apptools.io import File from apptools.naming.api import * class PyFSContextTestCase(unittest.TestCase): """ Tests naming operations on PyFS contexts. """ ########################################################################### # 'TestCase' interface. ########################################################################### def setUp(self): """ Prepares the test fixture before each test method is called. """ try: if os.path.exists('data'): shutil.rmtree('data') if os.path.exists('other'): shutil.rmtree('other') except: pass os.mkdir('data') os.mkdir('other') self.context = PyFSContext(path='data') self.context.create_subcontext('sub') self.context.bind('x', 123) self.context.bind('y', 321) return def tearDown(self): """ Called immediately after each test method has been called. """ self.context = None shutil.rmtree('data') shutil.rmtree('other') return ########################################################################### # Tests. ########################################################################### def test_initialization(self): """ initialization of an existing context """ context = PyFSContext(path='data') self.assertEqual(len(context.list_bindings('')), 3) return def test_initialization_with_empty_environment(self): """ initialization with empty environmentt """ context = PyFSContext(path='other', environment={}) self.assertEqual(len(context.list_names('')), 0) return def test_bind(self): """ pyfs context bind """ # Convenience. context = self.context sub = self.context.lookup('sub') # Make sure that the sub-context is empty. self.assertEqual(len(sub.list_bindings('')), 0) # Empty name. self.failUnlessRaises(InvalidNameError, context.bind, '', 1) # Bind a local file object. f = File(os.path.join(sub.path, 'foo.py')) #f.create_file('print "foo!"\n') context.bind('sub/foo.py', f) self.assertEqual(len(sub.list_bindings('')), 1) # Bind a reference to a non-local file. f = File('/tmp') context.bind('sub/tmp', f) self.assertEqual(len(sub.list_bindings('')), 2) self.assertEqual(context.lookup('sub/tmp').path, f.path) # Bind a reference to a non-local context. f = PyFSContext(path='other') context.bind('sub/other', f) self.assertEqual(len(sub.list_bindings('')), 3) self.assert_(f.path in context.lookup('sub/other').path) # Bind a Python object. context.bind('sub/a', 1) self.assertEqual(len(sub.list_bindings('')), 4) # Try to bind it again. self.failUnlessRaises(NameAlreadyBoundError, context.bind, 'sub/a', 1) return def test_rebind(self): """ pyfs context rebind """ # Convenience. context = self.context sub = self.context.lookup('sub') # Make sure that the sub-context is empty. self.assertEqual(len(sub.list_bindings('')), 0) # Empty name. self.failUnlessRaises(InvalidNameError, context.rebind, '', 1) # Bind a name. context.bind('sub/a', 1) self.assertEqual(len(sub.list_bindings('')), 1) # Rebind it. context.rebind('sub/a', 1) self.assertEqual(len(sub.list_bindings('')), 1) return def test_unbind(self): """ pyfs context unbind """ # Convenience. context = self.context sub = self.context.lookup('sub') # Make sure that the sub-context is empty. self.assertEqual(len(sub.list_bindings('')), 0) # Empty name. self.failUnlessRaises(InvalidNameError, context.unbind, '') # Bind a name. context.bind('sub/a', 1) self.assertEqual(len(sub.list_bindings('')), 1) # Unbind it. context.unbind('sub/a') self.assertEqual(len(sub.list_bindings('')), 0) # Try to unbind a non-existent name. self.failUnlessRaises(NameNotFoundError, context.unbind, 'sub/b') return def test_rename(self): """ multi-context rename """ # Convenience. context = self.context sub = self.context.lookup('sub') # Make sure that the sub-context is empty. self.assertEqual(len(sub.list_bindings('')), 0) # Empty name. self.failUnlessRaises(InvalidNameError, context.rename, '', 'x') self.failUnlessRaises(InvalidNameError, context.rename, 'x', '') # Bind a name. context.bind('sub/a', 1) self.assertEqual(len(sub.list_bindings('')), 1) # Rename it. context.rename('sub/a', 'sub/b') self.assertEqual(len(sub.list_bindings('')), 1) # Lookup using the new name. self.assertEqual(context.lookup('sub/b'), 1) # Lookup using the old name. self.failUnlessRaises(NameNotFoundError, context.lookup, 'sub/a') def test_lookup(self): """ pyfs context lookup """ # Convenience. context = self.context sub = self.context.lookup('sub') # Make sure that the sub-context is empty. self.assertEqual(len(sub.list_bindings('')), 0) # Bind a file object. f = File(os.path.join(sub.path, 'foo.py')) #f.create_file('print "foo!"\n') context.bind('sub/foo.py', f) self.assertEqual(len(sub.list_bindings('')), 1) # Look it up. self.assertEqual(context.lookup('sub/foo.py').path, f.path) # Bind a Python object. context.bind('sub/a', 1) self.assertEqual(len(sub.list_bindings('')), 2) # Look it up. self.assertEqual(context.lookup('sub/a'), 1) # Looking up the Empty name returns the context itself. self.assertEqual(context.lookup(''), context) # Non-existent name. self.failUnlessRaises(NameNotFoundError, context.lookup, 'sub/b') return def test_create_subcontext(self): """ pyfs context create sub-context """ # Convenience. context = self.context sub = self.context.lookup('sub') # Make sure that the sub-context is empty. self.assertEqual(len(sub.list_bindings('')), 0) # Empty name. self.failUnlessRaises(InvalidNameError, context.create_subcontext, '') # Create a sub-context. a = context.create_subcontext('sub/a') self.assertEqual(len(sub.list_bindings('')), 1) self.assert_(os.path.isdir(os.path.join(sub.path, 'a'))) # Try to bind it again. self.failUnlessRaises( NameAlreadyBoundError, context.create_subcontext, 'sub/a' ) return def test_destroy_subcontext(self): """ single context destroy sub-context """ # Convenience. context = self.context sub = self.context.lookup('sub') # Make sure that the sub-context is empty. self.assertEqual(len(sub.list_bindings('')), 0) # Empty name. self.failUnlessRaises(InvalidNameError, context.destroy_subcontext, '') # Create a sub-context. a = context.create_subcontext('sub/a') self.assertEqual(len(sub.list_bindings('')), 1) # Destroy it. context.destroy_subcontext('sub/a') self.assertEqual(len(sub.list_bindings('')), 0) self.assert_(not os.path.isdir(os.path.join(sub.path, 'a'))) # Bind a name. context.bind('sub/a', 1) self.assertEqual(len(sub.list_bindings('')), 1) # Try to destroy it. self.failUnlessRaises( NotContextError, context.destroy_subcontext, 'sub/a' ) return # Try to destroy a non-existent name. self.failUnlessRaises( NameNotFoundError, context.destroy_subcontext, 'sub/b' ) return def test_get_attributes(self): """ get attributes """ # Convenience. context = self.context sub = self.context.lookup('sub') self.assert_(isinstance(sub, DirContext)) #### Generic name resolution tests #### # Non-existent name. self.failUnlessRaises(NameNotFoundError, context.get_attributes, 'xx') # Attempt to resolve via a non-existent context. self.failUnlessRaises(NameNotFoundError, context.get_attributes,'xx/a') # Attempt to resolve via an existing name that is not a context. context.bind('sub/a', 1) self.assertEqual(len(sub.list_bindings('')), 1) self.failUnlessRaises(NotContextError,context.get_attributes,'sub/a/x') #### Operation specific tests #### # Attributes of the root context. attributes = context.get_attributes('') self.assertEqual(len(attributes), 0) # Attributes of a sub-context. attributes = context.get_attributes('sub') self.assertEqual(len(attributes), 0) return def test_set_get_attributes(self): """ get and set attributes """ defaults = {'colour' : 'blue'} # Convenience. context = self.context sub = self.context.lookup('sub') self.assert_(isinstance(sub, DirContext)) #### Generic name resolution tests #### # Non-existent name. self.failUnlessRaises( NameNotFoundError, context.set_attributes, 'xx', defaults ) # Attempt to resolve via a non-existent context. self.failUnlessRaises( NameNotFoundError, context.set_attributes, 'xx/a', defaults ) # Attempt to resolve via an existing name that is not a context. context.bind('sub/a', 1) self.assertEqual(len(sub.list_bindings('')), 1) self.failUnlessRaises( NotContextError, context.set_attributes, 'sub/a/xx', defaults ) #### Operation specific tests #### # Attributes of the root context. attributes = self.context.get_attributes('') self.assertEqual(len(attributes), 0) # Set the attributes. context.set_attributes('', defaults) attributes = context.get_attributes('') self.assertEqual(len(attributes), 1) self.assertEqual(attributes['colour'], 'blue') # Attributes of a sub-context. attributes = context.get_attributes('sub') self.assertEqual(len(attributes), 0) # Set the attributes. context.set_attributes('sub', defaults) attributes = context.get_attributes('sub') self.assertEqual(len(attributes), 1) self.assertEqual(attributes['colour'], 'blue') return def test_namespace_name(self): """ get the name of a context within its namespace. """ # Convenience. context = self.context sub = self.context.lookup('sub') self.assert_(isinstance(sub, DirContext)) self.assertEqual(context.namespace_name, 'data') self.assertEqual(sub.namespace_name, 'data/sub') return #### EOF ###################################################################### apptools-4.1.0/apptools/naming/tests/context_test_case.py0000644000175100001440000004130511674464005024714 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Tests operations that span contexts. """ # Standard library imports. import unittest # Enthought library imports. from apptools.naming.api import * class ContextTestCase(unittest.TestCase): """ Tests naming operations that span contexts. """ ########################################################################### # 'TestCase' interface. ########################################################################### def setUp(self): """ Prepares the test fixture before each test method is called. """ self.context = self.create_context() self.context.create_subcontext('sub') return def tearDown(self): """ Called immediately after each test method has been called. """ self.context = None return ########################################################################### # 'ContextTestCase' interface. ########################################################################### def create_context(self): """ Creates the context that we are testing. """ return Context() ########################################################################### # Tests. ########################################################################### def test_bind(self): """ bind """ # Convenience. context = self.context sub = self.context.lookup('sub') self.assert_(isinstance(sub, Context)) # Make sure that the sub-context is empty. self.assertEqual(len(sub.list_bindings('')), 0) # Empty name. self.failUnlessRaises(InvalidNameError, context.bind, '', 1) # Attempt to resolve via a non-existent context. self.failUnlessRaises(NameNotFoundError, context.bind, 'xx/a', 1) self.assertEqual(len(sub.list_bindings('')), 0) # Bind a name. context.bind('sub/a', 1) self.assertEqual(len(sub.list_bindings('')), 1) # Attempt to resolve via an existing name that is not a context. self.failUnlessRaises(NotContextError, context.bind, 'sub/a/xx', 1) self.assertEqual(len(sub.list_bindings('')), 1) # Try to bind it again. self.failUnlessRaises(NameAlreadyBoundError, context.bind, 'sub/a', 1) return def test_bind_with_make_contexts(self): """ bind with make contexts """ # Convenience. context = self.context sub = self.context.lookup('sub') self.assert_(isinstance(sub, Context)) # Make sure that the sub-context is empty. self.assertEqual(len(sub.list_bindings('')), 0) # Empty name. self.failUnlessRaises(InvalidNameError, context.bind, '', 1, True) # Attempt to resolve via a non-existent context - which should result # in the context being created automatically. context.bind('xx/a', 1, True) self.assertEqual(len(context.list_bindings('xx')), 1) self.assertEqual(1, context.lookup('xx/a')) # Bind an even more 'nested' name. context.bind('xx/foo/bar/baz', 42, True) self.assertEqual(len(context.list_bindings('xx/foo/bar')), 1) self.assertEqual(42, context.lookup('xx/foo/bar/baz')) return def test_rebind(self): """ context rebind """ # Convenience. context = self.context sub = self.context.lookup('sub') # Make sure that the sub-context is empty. self.assertEqual(len(sub.list_bindings('')), 0) # Empty name. self.failUnlessRaises(InvalidNameError, context.rebind, '', 1) # Attempt to resolve via a non-existent context. self.failUnlessRaises(NameNotFoundError, context.rebind, 'xx/a', 1) self.assertEqual(len(sub.list_bindings('')), 0) # Bind a name. context.bind('sub/a', 1) self.assertEqual(len(sub.list_bindings('')), 1) # Rebind it. context.rebind('sub/a', 1) self.assertEqual(len(sub.list_bindings('')), 1) # Attempt to resolve via an existing name that is not a context. self.failUnlessRaises(NotContextError, context.rebind, 'sub/a/xx', 1) self.assertEqual(len(sub.list_bindings('')), 1) return def test_rebind_with_make_contexts(self): """ rebind with make contexts """ # Convenience. context = self.context sub = self.context.lookup('sub') self.assert_(isinstance(sub, Context)) # Make sure that the sub-context is empty. self.assertEqual(len(sub.list_bindings('')), 0) # Empty name. self.failUnlessRaises(InvalidNameError, context.rebind, '', 1, True) # Attempt to resolve via a non-existent context - which should result # in the context being created automatically. context.rebind('xx/a', 1, True) self.assertEqual(len(context.list_bindings('xx')), 1) self.assertEqual(1, context.lookup('xx/a')) # Rebind an even more 'nested' name. context.rebind('xx/foo/bar/baz', 42, True) self.assertEqual(len(context.list_bindings('xx/foo/bar')), 1) self.assertEqual(42, context.lookup('xx/foo/bar/baz')) # And do it again... (this is REbind after all). context.rebind('xx/foo/bar/baz', 42, True) self.assertEqual(len(context.list_bindings('xx/foo/bar')), 1) self.assertEqual(42, context.lookup('xx/foo/bar/baz')) return def test_unbind(self): """ context unbind """ # Convenience. context = self.context sub = self.context.lookup('sub') # Make sure that the sub-context is empty. self.assertEqual(len(sub.list_bindings('')), 0) # Empty name. self.failUnlessRaises(InvalidNameError, context.unbind, '') # Attempt to resolve via a non-existent context. self.failUnlessRaises(NameNotFoundError, context.unbind, 'xx/a') self.assertEqual(len(sub.list_bindings('')), 0) # Bind a name. context.bind('sub/a', 1) self.assertEqual(len(sub.list_bindings('')), 1) # Attempt to resolve via an existing name that is not a context. self.failUnlessRaises(NotContextError, context.unbind, 'sub/a/xx') self.assertEqual(len(sub.list_bindings('')), 1) # Unbind it. context.unbind('sub/a') self.assertEqual(len(sub.list_bindings('')), 0) # Try to unbind a non-existent name. self.failUnlessRaises(NameNotFoundError, context.unbind, 'sub/b') return def test_rename_object(self): """ rename an object """ # Convenience. context = self.context sub = self.context.lookup('sub') # Make sure that the sub-context is empty. self.assertEqual(len(sub.list_bindings('')), 0) # Empty name. self.failUnlessRaises(InvalidNameError, context.rename, '', 'x') self.failUnlessRaises(InvalidNameError, context.rename, 'x', '') # Attempt to resolve via a non-existent context. self.failUnlessRaises(NameNotFoundError, context.rename, 'x/a', 'x/b') self.assertEqual(len(sub.list_bindings('')), 0) # Bind a name. context.bind('sub/a', 1) self.assertEqual(len(sub.list_bindings('')), 1) # Attempt to resolve via an existing name that is not a context. self.failUnlessRaises(NotContextError, context.bind, 'sub/a/xx', 1) self.assertEqual(len(sub.list_bindings('')), 1) # Rename it. context.rename('sub/a', 'sub/b') self.assertEqual(len(sub.list_bindings('')), 1) # Lookup using the new name. self.assertEqual(context.lookup('sub/b'), 1) # Lookup using the old name. self.failUnlessRaises(NameNotFoundError, context.lookup, 'sub/a') return def test_rename_context(self): """ rename a context """ # Convenience. context = self.context sub = self.context.lookup('sub') # Make sure that the sub-context is empty. self.assertEqual(len(sub.list_bindings('')), 0) # Empty name. self.failUnlessRaises(InvalidNameError, context.rename, '', 'x') self.failUnlessRaises(InvalidNameError, context.rename, 'x', '') # Attempt to resolve via a non-existent context. self.failUnlessRaises(NameNotFoundError, context.rename, 'x/a', 'x/b') self.assertEqual(len(sub.list_bindings('')), 0) # Create a context. context.create_subcontext('sub/a') self.assertEqual(len(sub.list_bindings('')), 1) # Rename it. context.rename('sub/a', 'sub/b') self.assertEqual(len(sub.list_bindings('')), 1) # Lookup using the new name. context.lookup('sub/b') # Lookup using the old name. self.failUnlessRaises(NameNotFoundError, context.lookup, 'sub/a') return def test_lookup(self): """ lookup """ # Convenience. context = self.context sub = self.context.lookup('sub') # Make sure that the sub-context is empty. self.assertEqual(len(sub.list_bindings('')), 0) # Attempt to resolve via a non-existent context. self.failUnlessRaises(NameNotFoundError, context.lookup, 'xx/a') self.assertEqual(len(sub.list_bindings('')), 0) # Bind a name. context.bind('sub/a', 1) self.assertEqual(len(sub.list_bindings('')), 1) # Attempt to resolve via an existing name that is not a context. self.failUnlessRaises(NotContextError, context.lookup, 'sub/a/xx') self.assertEqual(len(sub.list_bindings('')), 1) # Look it up. self.assertEqual(context.lookup('sub/a'), 1) # Looking up the Empty name returns the context itself. self.assertEqual(context.lookup(''), context) # Non-existent name. self.failUnlessRaises(NameNotFoundError, context.lookup, 'sub/b') return def test_create_subcontext(self): """ create sub-context """ # Convenience. context = self.context sub = self.context.lookup('sub') # Make sure that the sub-context is empty. self.assertEqual(len(sub.list_bindings('')), 0) # Empty name. self.failUnlessRaises(InvalidNameError, context.create_subcontext, '') # Attempt to resolve via a non-existent context. self.failUnlessRaises( NameNotFoundError, context.create_subcontext, 'xx/a' ) self.assertEqual(len(sub.list_bindings('')), 0) # Create a sub-context. context.create_subcontext('sub/a') self.assertEqual(len(sub.list_bindings('')), 1) # Try to bind it again. self.failUnlessRaises( NameAlreadyBoundError, context.create_subcontext, 'sub/a' ) # Bind a name. context.bind('sub/b', 1) self.assertEqual(len(sub.list_bindings('')), 2) # Attempt to resolve via an existing name that is not a context. self.failUnlessRaises( NotContextError, context.create_subcontext, 'sub/b/xx' ) self.assertEqual(len(sub.list_bindings('')), 2) return def test_destroy_subcontext(self): """ single context destroy sub-context """ # Convenience. context = self.context sub = self.context.lookup('sub') # Make sure that the sub-context is empty. self.assertEqual(len(sub.list_bindings('')), 0) # Empty name. self.failUnlessRaises(InvalidNameError, context.destroy_subcontext, '') # Attempt to resolve via a non-existent context. self.failUnlessRaises( NameNotFoundError, context.destroy_subcontext, 'xx/a' ) self.assertEqual(len(sub.list_bindings('')), 0) # Create a sub-context. context.create_subcontext('sub/a') self.assertEqual(len(sub.list_bindings('')), 1) # Destroy it. context.destroy_subcontext('sub/a') self.assertEqual(len(sub.list_bindings('')), 0) # Bind a name. context.bind('sub/a', 1) self.assertEqual(len(sub.list_bindings('')), 1) # Try to destroy it. self.failUnlessRaises( NotContextError, context.destroy_subcontext, 'sub/a' ) # Try to destroy a non-existent name. self.failUnlessRaises( NameNotFoundError, context.destroy_subcontext, 'sub/b' ) # Attempt to resolve via an existing name that is not a context. self.failUnlessRaises( NotContextError, context.destroy_subcontext, 'sub/a/xx' ) self.assertEqual(len(sub.list_bindings('')), 1) return def test_list_bindings(self): """ list bindings """ # Convenience. context = self.context sub = self.context.lookup('sub') # List the bindings in the root. bindings = context.list_bindings('') self.assertEqual(len(bindings), 1) # List the names in the sub-context. bindings = context.list_bindings('sub') self.assertEqual(len(bindings), 0) # Attempt to resolve via a non-existent context. self.failUnlessRaises(NameNotFoundError, context.list_bindings, 'xx/a') self.assertEqual(len(sub.list_bindings('')), 0) # Bind a name. context.bind('sub/a', 1) self.assertEqual(len(sub.list_bindings('')), 1) # Attempt to resolve via an existing name that is not a context. self.failUnlessRaises( NotContextError, context.list_bindings, 'sub/a/xx' ) self.assertEqual(len(sub.list_bindings('')), 1) return def test_list_names(self): """ list names """ # Convenience. context = self.context sub = self.context.lookup('sub') # List the names in the root. names = context.list_names('') self.assertEqual(len(names), 1) # List the names in the sub-context. names = context.list_names('sub') self.assertEqual(len(names), 0) # Attempt to resolve via a non-existent context. self.failUnlessRaises(NameNotFoundError, context.list_names, 'xx/a') self.assertEqual(len(sub.list_bindings('')), 0) # Bind a name. context.bind('sub/a', 1) self.assertEqual(len(sub.list_bindings('')), 1) # Attempt to resolve via an existing name that is not a context. self.failUnlessRaises( NotContextError, context.list_names, 'sub/a/xx' ) self.assertEqual(len(sub.list_bindings('')), 1) return def test_default_factories(self): """ default object and state factories. """ object_factory = ObjectFactory() self.failUnlessRaises( NotImplementedError, object_factory.get_object_instance, 0, 0, 0 ) state_factory = StateFactory() self.failUnlessRaises( NotImplementedError, state_factory.get_state_to_bind, 0, 0, 0 ) return def test_search(self): """ test retrieving the names of bound objects """ # Convenience. context = self.context sub = self.context.lookup('sub') sub_sibling = context.create_subcontext('sub sibling') sub_sub = sub.create_subcontext('sub sub') context.bind('one',1) names = context.search( 1 ) self.assertEqual( len(names), 1) self.assertEqual( names[0], 'one' ) names = sub.search(1) self.assertEqual( len(names), 0) context.bind('sub/two',2) names = context.search( 2 ) self.assertEqual( len(names), 1) self.assertEqual( names[0], 'sub/two' ) names = sub.search( 2 ) self.assertEqual( len(names), 1) self.assertEqual( names[0], 'two' ) context.bind('sub/sub sub/one',1) names = context.search( 1 ) self.assertEqual( len(names), 2) self.assertEqual( names, ['one', 'sub/sub sub/one'] ) names = sub.search(None) self.assertEqual( len(names), 0) names = context.search( sub_sub ) self.assertEqual( len(names), 1) self.assertEqual( names[0], 'sub/sub sub' ) return #### EOF ###################################################################### apptools-4.1.0/apptools/naming/tests/dir_context_test_case.py0000644000175100001440000001024111674464005025545 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Tests the default directory context. """ # Enthought library imports. from apptools.naming.api import * # Local imports. from context_test_case import ContextTestCase class DirContextTestCase(ContextTestCase): """ Tests the default directory context. """ ########################################################################### # 'ContextTestCase' interface. ########################################################################### def create_context(self): """ Creates the context that we are testing. """ return DirContext() ########################################################################### # Tests. ########################################################################### def test_get_attributes(self): """ get attributes """ # Convenience. context = self.context sub = self.context.lookup('sub') self.assert_(isinstance(sub, DirContext)) #### Generic name resolution tests #### # Non-existent name. self.failUnlessRaises(NameNotFoundError, context.get_attributes, 'x') # Attempt to resolve via a non-existent context. self.failUnlessRaises(NameNotFoundError, context.get_attributes, 'x/a') # Attempt to resolve via an existing name that is not a context. context.bind('sub/a', 1) self.assertEqual(len(sub.list_bindings('')), 1) self.failUnlessRaises(NotContextError,context.get_attributes,'sub/a/x') #### Operation specific tests #### # Attributes of the root context. attributes = self.context.get_attributes('') self.assertEqual(len(attributes), 0) # Attributes of a sub-context. attributes = context.get_attributes('sub') self.assertEqual(len(attributes), 0) return def test_set_get_attributes(self): """ get and set attributes """ defaults = {'colour' : 'blue'} # Convenience. context = self.context sub = self.context.lookup('sub') self.assert_(isinstance(sub, DirContext)) #### Generic name resolution tests #### # Non-existent name. self.failUnlessRaises( NameNotFoundError, context.set_attributes, 'x', defaults ) # Attempt to resolve via a non-existent context. self.failUnlessRaises( NameNotFoundError, context.set_attributes, 'x/a', defaults ) # Attempt to resolve via an existing name that is not a context. context.bind('sub/a', 1) self.assertEqual(len(sub.list_bindings('')), 1) self.failUnlessRaises( NotContextError, context.set_attributes, 'sub/a/xx', defaults ) #### Operation specific tests #### # Attributes of the root context. attributes = self.context.get_attributes('') self.assertEqual(len(attributes), 0) # Set the attributes. context.set_attributes('', defaults) attributes = context.get_attributes('') self.assertEqual(len(attributes), 1) self.assertEqual(attributes['colour'], 'blue') # Attributes of a sub-context. attributes = context.get_attributes('sub') self.assertEqual(len(attributes), 0) # Set the attributes. context.set_attributes('sub', defaults) attributes = context.get_attributes('sub') self.assertEqual(len(attributes), 1) self.assertEqual(attributes['colour'], 'blue') return #### EOF ###################################################################### apptools-4.1.0/apptools/naming/tests/py_context_test_case.py0000644000175100001440000000244611674464005025427 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Tests the Python namespace context. """ # Enthought library imports. from apptools.naming.api import PyContext # Local imports. from context_test_case import ContextTestCase class PyContextTestCase(ContextTestCase): """ Tests the Python namespace context. """ ########################################################################### # 'ContextTestCase' interface. ########################################################################### def create_context(self): """ Creates the context that we are testing. """ return PyContext(namespace={}) #### EOF ###################################################################### apptools-4.1.0/apptools/naming/pyfs_initial_context_factory.py0000644000175100001440000000407111674464005026020 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The initial context factory for Python file system contexts. """ # Local imports. from context import Context from initial_context_factory import InitialContextFactory from object_serializer import ObjectSerializer from pyfs_context import PyFSContext from pyfs_context_factory import PyFSContextFactory from pyfs_object_factory import PyFSObjectFactory from pyfs_state_factory import PyFSStateFactory class PyFSInitialContextFactory(InitialContextFactory): """ The initial context factory for Python file system contexts. """ ########################################################################### # 'InitialContextFactory' interface. ########################################################################### def get_initial_context(self, environment): """ Creates an initial context for beginning name resolution. """ # Object factories. object_factories = [PyFSObjectFactory(), PyFSContextFactory()] environment[Context.OBJECT_FACTORIES] = object_factories # State factories. state_factories = [PyFSStateFactory()] environment[Context.STATE_FACTORIES] = state_factories # Object serializers. object_serializers = [ObjectSerializer()] environment[PyFSContext.OBJECT_SERIALIZERS] = object_serializers return PyFSContext(path=r'', environment=environment) #### EOF ###################################################################### apptools-4.1.0/apptools/naming/ui/0000755000175100001440000000000011674464005020074 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/naming/ui/object_node_type.py0000644000175100001440000000453211674464005023766 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The node type for NON-contexts in a naming system. """ # Enthought library imports. from apptools.naming.api import Context from pyface.tree.api import NodeType class ObjectNodeType(NodeType): """ The node type for NON-contexts in a naming system. """ ########################################################################### # 'NodeType' interface. ########################################################################### # 'node' in this case is a 'Binding' instance. def is_type_for(self, node): """ Returns True if this node type recognized a node. """ return True def allows_children(self, node): """ Does the node allow children (ie. a folder vs a file). """ return False def get_drag_value(self, node): """ Get the value that is dragged for a node. """ return node.obj def is_editable(self, node): """ Returns True if the node is editable, otherwise False. If the node is editable, its text can be set via the UI. """ return True def get_text(self, node): """ Returns the label text for a node. """ return node.name def can_set_text(self, node, text): """ Returns True if the node's label can be set. """ # The parent context will NOT be None here (an object is ALWAYS # contained in a context). parent = node.context return len(text.strip()) > 0 and text not in parent.list_names('') def set_text(self, node, text): """ Sets the label text for a node. """ node.context.rename(node.name, text) node.name = text return ##### EOF ##################################################################### apptools-4.1.0/apptools/naming/ui/naming_node_manager.py0000644000175100001440000000363511674464005024425 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The node manager for a naming tree. """ # Enthought library imports. from pyface.tree.api import NodeManager class NamingNodeManager(NodeManager): """ The node manager for a naming tree. """ ########################################################################### # 'NodeManager' interface. ########################################################################### def get_key(self, node): """ Generates a unique key for a node. """ # fixme: This scheme does NOT allow the same object to be in the tree # in more than one place. return self._get_hash_value(node.obj) ########################################################################### # Private interface. ########################################################################### def _get_hash_value(self, obj): """ Returns a hash value for an object. """ # We do it like this 'cos, for example, using id() on a string # doesn't give us what we want, but things like lists aren't # hashable, so we can't always use hash()). try: hash_value = hash(obj) except: hash_value = id(obj) return hash_value ##### EOF ##################################################################### apptools-4.1.0/apptools/naming/ui/images/0000755000175100001440000000000011674464005021341 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/naming/ui/images/open_folder.png0000644000175100001440000000115111674464005024341 0ustar ischnellusers00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<ûIDATxÚbd```ê.øË€J»>h©Û@ü›  æÿ?ÞíÅÀ q ¼s1 31#º~€I0üþr ÃàÜŠµ@2x/!WØ€ÿÿ¾C¹ÿ@>Ó¿>†‚ ‚¯ q2N1HÓ€ðï÷; h(#+XÙ·[¯{ŒËË`W]¹ý»háú¯3jÀ¸‚¿?€5gÏe`bÀv“;‚û€Ô5€‚ðç\âÛ‡« p¹ôÚ/°wþÿyùÃ÷Wë8D¼ÍÑ Hüýæýù~ŸaÕŽï ©)É@C?‚$þÿÿ¡ÿýaøýû/Ûï ó—ìè –ÇõÄoÞ~†%H3Ã_° ¶ÿ…4ÃÏßÿ¸þ~…Ùþˆ<?ù°ãð†Ä7ˆ—þƒbä?Ü ö? µ`ùQ˜íÇø@ xõæ3î£? Þù V áüzáÔ+~þúlûa¯lH³ƒˆ¤!PíHÀýÿwÌ :¨í tÀ Ò @ ¤©ŒÛ« D ý@ªˆŸL  ÔcÄ &D@?Èy€ø(8^ €`™D³C Ã@¹öL3ãÿÿÿ(0ŒþëøIEND®B`‚apptools-4.1.0/apptools/naming/ui/images/document.png0000644000175100001440000000051511674464005023666 0ustar ischnellusers00000000000000‰PNG  IHDRóÿabKGDÿÿÿ ½§“ pHYs  šœtIMEÕ8º`&BÚIDAT8Ë­’=n„0…¿H»[!!ŸŸ³l™"ÇAæ!QÚsú­LŠˆ$^X¢¼j,{Þ|3µmû1 à ÇôZ×õ;ÖÚ騬µÓì”ÌÁ8Žxït/5Ç’ˆã˜4MW(wï=ιUÂR’¨ªêW/ÉòPÅ&AHÉòá’à§ž"ÈóBbh H(ýüÿ÷j;Ä`À®€€B‰FpXüÿq R@ ú6äïßß( ÄéŸ]qDˆ qú?°Ìß Pï€Á€%K-`¼’°¹ H­ ¬@lÄÎ@,D¤þï@|ˆ÷,c€hv¨aÄPî΀bü 4r@€œô2s˜^IEND®B`‚apptools-4.1.0/apptools/naming/ui/images/image_LICENSE.txt0000644000175100001440000000067111674464005024332 0ustar ischnellusers00000000000000The icons are mostly derived work from other icons. As such they are licensed accordingly to the original license: GV: Gael Varoquaux: BSD-like Unless stated in this file, icons are work of enthought, and are released under BSD-like license. Files and orginal authors: ---------------------------------------------------------------- closed_folder.png | GV document.png | GV open_folder.png | GV apptools-4.1.0/apptools/naming/ui/api.py0000644000175100001440000000165711674464005021230 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ from context_monitor import ContextMonitor from context_node_type import ContextNodeType from explorer import Explorer, explore from naming_node_manager import NamingNodeManager from naming_tree import NamingTree from naming_tree_model import NamingTreeModel from object_node_type import ObjectNodeType apptools-4.1.0/apptools/naming/ui/naming_tree_model.py0000644000175100001440000000377111674464005024126 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The model for a tree view of a naming system. """ # Enthought library imports. from apptools.naming.api import Binding from pyface.tree.api import NodeTreeModel from traits.api import Instance # Local imports. from context_node_type import ContextNodeType from naming_node_manager import NamingNodeManager from object_node_type import ObjectNodeType class NamingTreeModel(NodeTreeModel): """ The model for a tree view of a naming system. """ #### 'TreeModel' interface ################################################ # The root of the model. root = Instance(Binding) #### 'NodeTreeModel' interface ############################################ # The node manager looks after all node types. node_manager = Instance(NamingNodeManager) ########################################################################### # 'NodeTreeModel' interface. ########################################################################### #### Trait initializers ################################################### def _node_manager_default(self): """ Initializes the node manaber trait. """ node_manager = NamingNodeManager() node_manager.add_node_type(ContextNodeType()) node_manager.add_node_type(ObjectNodeType()) return node_manager ##### EOF ##################################################################### apptools-4.1.0/apptools/naming/ui/naming_tree.py0000644000175100001440000000517611674464005022747 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A tree view of a naming system. """ # Enthought library imports. from apptools.naming.api import OperationNotSupportedError from pyface.tree.api import NodeTree from traits.api import Instance # Local imports. from naming_tree_model import NamingTreeModel class NamingTree(NodeTree): """ A tree view of a naming system. """ #### 'Tree' interface ##################################################### # The model that provides the data for the tree. model = Instance(NamingTreeModel) ########################################################################### # 'Tree' interface. ########################################################################### #### Trait initializers ################################################### def _model_default(self): """ Initializes the model trait. """ return NamingTreeModel() ########################################################################### # 'NamingTree' interface. ########################################################################### def ensure_visible(self, node): """ Make sure that the specified node is visible. """ try: components = node.namespace_name.split('/') # Make sure that the tree is expanded down to the context that # contains the node. binding = self.root for atom in components[:-1]: binding = binding.obj.lookup_binding(atom) self.expand(binding) # The context is expanded so we know that the node will be in the # node to Id map. wxid = self._node_to_id_map.get(self.model.get_key(node), None) self.control.EnsureVisible(wxid) # We need 'namespace_name' to make this work. If we don't have it # then we simply cannot do this! except OperationNotSupportedError: binding = None return binding ##### EOF ##################################################################### apptools-4.1.0/apptools/naming/ui/context_monitor.py0000644000175100001440000000705611674464005023711 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A monitor that detects changes to a naming context. """ # Enthought library imports. from pyface.tree.api import NodeMonitor class ContextMonitor(NodeMonitor): """ A monitor that detects changes to a naming context. """ ########################################################################### # 'NodeMonitor' interface. ########################################################################### def start(self): """ Start listening to changes to the object. """ context = self.node.obj context.on_trait_change(self._on_object_added, 'object_added') context.on_trait_change(self._on_object_changed, 'object_changed') context.on_trait_change(self._on_object_removed, 'object_removed') context.on_trait_change(self._on_object_renamed, 'object_renamed') context.on_trait_change(self._on_context_changed, 'context_changed') return def stop(self): """ Stop listening to changes to the object. """ context = self.node.obj context.on_trait_change( self._on_object_added, 'object_added', remove=True ) context.on_trait_change( self._on_object_changed, 'object_changed', remove=True ) context.on_trait_change( self._on_object_removed, 'object_removed', remove=True ) context.on_trait_change( self._on_object_renamed, 'object_renamed', remove=True ) context.on_trait_change( self._on_context_changed, 'context_changed', remove=True ) return ########################################################################### # Private interface. ########################################################################### #### Trait event handlers ################################################# def _on_object_added(self, event): """ Called when an object has been added to the context. """ self.fire_nodes_inserted([event.new_binding]) return def _on_object_changed(self, event): """ Called when an object in the context has been changed. """ # fixme: Can we get enough information to fire a 'nodes_replaced' # event? Something a little more granular than this! self.fire_structure_changed() return def _on_object_removed(self, event): """ Called when an object has been removed from the context. """ self.fire_nodes_removed([event.old_binding]) return def _on_object_renamed(self, event): """ Called when an object has been renamed in the context. """ self.fire_nodes_replaced([event.old_binding], [event.new_binding]) return def _on_context_changed(self, event): """ Called when a context has changed dramatically. """ self.fire_structure_changed() return ##### EOF ##################################################################### apptools-4.1.0/apptools/naming/ui/context_node_type.py0000644000175100001440000000602211674464005024200 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The node type for contexts in a naming system. """ # Enthought library imports. from apptools.naming.api import Context from pyface.tree.api import NodeType # Local imports. from context_monitor import ContextMonitor class ContextNodeType(NodeType): """ The node type for contexts in a naming system. """ ########################################################################### # 'NodeType' interface. ########################################################################### # 'node' in this case is a 'Binding' instance whose 'obj' trait is a # 'Context' instance. def is_type_for(self, node): """ Returns True if this node type recognizes a node. """ return isinstance(node.obj, Context) def allows_children(self, node): """ Does the node allow children (ie. a folder vs a file). """ return True def has_children(self, node): """ Returns True if a node has children, otherwise False. """ return len(node.obj.list_names('')) > 0 def get_children(self, node): """ Returns the children of a node. """ return node.obj.list_bindings('') def get_drag_value(self, node): """ Get the value that is dragged for a node. """ return node.obj def is_editable(self, node): """ Returns True if the node is editable, otherwise False. """ # You can't rename the root context! return node.context is not None def get_text(self, node): """ Returns the label text for a node. """ return node.name def can_set_text(self, node, text): """ Returns True if the node's label can be set. """ # The parent context will NOT be None here (see 'is_editable'). parent = node.context return len(text.strip()) > 0 and text not in parent.list_names('') def set_text(self, node, text): """ Sets the label text for a node. """ print 'Setting text on', node.name, node.obj print 'Context details', node.obj.name, node.obj.path # Do the rename in the naming system. node.context.rename(node.name, text) # Update the binding. node.name = text return def get_monitor(self, node): """ Returns a monitor that detects changes to a node. """ return ContextMonitor(node=node) ##### EOF ##################################################################### apptools-4.1.0/apptools/naming/ui/__init__.py0000644000175100001440000000120011674464005022176 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ apptools-4.1.0/apptools/naming/ui/explorer.py0000644000175100001440000000625611674464005022317 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A naming system explorer. """ # Enthought library imports. from apptools.naming.api import Binding, PyContext from pyface.api import PythonShell, SplitApplicationWindow from traits.api import Float, Instance, Str # Local imports. from naming_tree import NamingTree # Factory function for exploring a Python namespace. def explore(obj): """ View a Python object as a naming context. """ root = Binding(name='root', obj=PyContext(namespace=obj)) explorer = Explorer(root=root, size=(1200, 400)) explorer.open() return class Explorer(SplitApplicationWindow): """ The main application window. """ #### 'Window' interface ################################################### title = Str('Naming System Explorer') #### 'SplitApplicationWindow' interface ################################### # The direction in which the panel is split. direction = Str('vertical') # The ratio of the size of the left/top pane to the right/bottom pane. ratio = Float(0.3) # The root binding (usually a binding to a context!). root = Instance(Binding) ########################################################################### # Protected 'SplitApplicationWindow' interface. ########################################################################### def _create_lhs(self, parent): """ Creates the left hand side or top depending on the style. """ return self._create_tree(parent, self.root) def _create_rhs(self, parent): """ Creates the panel containing the selected preference page. """ return self._create_python_shell(parent) ########################################################################### # Private interface. ########################################################################### def _create_tree(self, parent, root): """ Creates the tree. """ self._tree = tree = NamingTree(parent, root=root) return tree.control def _create_python_shell(self, parent): """ Creates the Python shell. """ self._python_shell = python_shell = PythonShell(parent) # Bind useful names. python_shell.bind('widget', self._tree) python_shell.bind('w', self._tree) python_shell.bind('window', self) python_shell.bind('explore', explore) # Execute useful commands to bind useful names ;^) python_shell.execute_command('from apptools.naming.api import *') return python_shell.control ##### EOF ##################################################################### apptools-4.1.0/apptools/naming/api.py0000644000175100001440000000350711674464005020607 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ from exception import NamingError, InvalidNameError, NameAlreadyBoundError from exception import NameNotFoundError, NotContextError from exception import OperationNotSupportedError from address import Address from binding import Binding from context import Context from context_adapter import ContextAdapter from context_adapter_factory import ContextAdapterFactory from dynamic_context import DynamicContext from dir_context import DirContext from initial_context import InitialContext from initial_context_factory import InitialContextFactory from naming_event import NamingEvent from naming_manager import naming_manager from object_factory import ObjectFactory from object_serializer import ObjectSerializer from py_context import PyContext from py_object_factory import PyObjectFactory from pyfs_context import PyFSContext from pyfs_context_factory import PyFSContextFactory from pyfs_initial_context_factory import PyFSInitialContextFactory from pyfs_object_factory import PyFSObjectFactory from pyfs_state_factory import PyFSStateFactory from reference import Reference from referenceable import Referenceable from referenceable_state_factory import ReferenceableStateFactory from state_factory import StateFactory apptools-4.1.0/apptools/naming/unique_name.py0000644000175100001440000000156311674464005022344 0ustar ischnellusers00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2007 by Enthought, Inc. # All rights reserved. # #----------------------------------------------------------------------------- """ A re-usable method for calculating a unique name given a list of existing names. """ def make_unique_name(base, existing=[], format="%s_%s"): """ Return a name, unique within a context, based on the specified name. base: the desired base name of the generated unique name. existing: a sequence of the existing names to avoid returning. format: a formatting specification for how the name is made unique. """ count = 2 name = base while name in existing: name = format % (base, count) count += 1 return name #### EOF #################################################################### apptools-4.1.0/apptools/naming/naming_event.py0000644000175100001440000000215211674464005022503 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The event fired by the tree model when it changes. """ # Enthought library imports. from traits.api import HasTraits, Instance # Local imports. from binding import Binding # Classes for event traits. class NamingEvent(HasTraits): """ Information about tree model changes. """ # The old binding. old_binding = Instance(Binding) # The new binding. new_binding = Instance(Binding) #### EOF ###################################################################### apptools-4.1.0/apptools/naming/initial_context.py0000644000175100001440000000421511674464005023230 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The starting point for performing naming operations. """ # Local imports. from context import Context def InitialContext(environment): """ Creates an initial context for beginning name resolution. """ # Get the class name of the factory that will produce the initial context. klass_name = environment.get(Context.INITIAL_CONTEXT_FACTORY) if klass_name is None: raise ValueError("No initial context factory specified") # Import the factory class. klass = _import_symbol(klass_name) # Create the factory. factory = klass() # Ask the factory for a context implementation instance. return factory.get_initial_context(environment) # fixme: This is the same code as in the Envisage import manager but we don't # want naming to be dependent on Envisage, so we need some other package # for useful 'Python' tools etc. def _import_symbol(symbol_path): """ Imports the symbol defined by 'symbol_path'. 'symbol_path' is a string in the form 'foo.bar.baz' which is turned into an import statement 'from foo.bar import baz' (ie. the last component of the name is the symbol name, the rest is the package/ module path to load it from). """ components = symbol_path.split('.') module_name = '.'.join(components[:-1]) symbol_name = components[-1] module = __import__(module_name, globals(), locals(), [symbol_name]) symbol = getattr(module, symbol_name) return symbol #### EOF ###################################################################### apptools-4.1.0/apptools/naming/referenceable_state_factory.py0000644000175100001440000000272211674464005025545 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ State factory for referenceable objects. """ # Local imports. from referenceable import Referenceable from state_factory import StateFactory class ReferenceableStateFactory(StateFactory): """ State factory for referenceable objects. """ ########################################################################### # 'StateFactory' interface. ########################################################################### def get_state_to_bind(self, obj, name, context): """ Returns the state of an object for binding. """ state = None # If the object knows how to create a reference to it then let it # do so. if isinstance(obj, Referenceable): state = obj.reference return state ### EOF ####################################################################### apptools-4.1.0/apptools/naming/binding.py0000644000175100001440000000666311674464005021456 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The representation of a name-to-object binding in a context. """ # Enthought libary imports. from traits.api import Any, HasTraits, Property, Str class Binding(HasTraits): """ The representation of a name-to-object binding in a context. """ #### 'Binding' interface ################################################## # The class name of the object bound to the name in the binding. class_name = Property(Str) # The name. name = Str # The object bound to the name in the binding. obj = Any #### Experimental 'Binding' interface ##################################### # fixme: These is not part of the JNDI spec, but they do seem startlingly # useful! # # fixme: And, errr, startlingly broken! If the context that the binding # is in is required then just look up the name minus the last component! # # The context that the binding came from. context = Any # The name of the bound object within the namespace. namespace_name = Property(Str) #### 'Private' interface ################################################## # Shadow trait for the 'class_name' property. _class_name = Str ########################################################################### # 'object' interface. ########################################################################### def __str__(self): """ Returns an informal string representation of the object. """ return super(Binding, self).__str__() + '(name=%s, obj=%s)' % ( self.name, self.obj) ########################################################################### # 'Binding' interface. ########################################################################### #### Properties ########################################################### # class_name def _get_class_name(self): """ Returns the class name of the object. """ if len(self._class_name) == 0: if self.obj is None: class_name = None else: klass = self.obj.__class__ class_name = '%s.%s' % (klass.__module__, klass.__name__) return class_name def _set_class_name(self, class_name): """ Sets the class name of the object. """ self._class_name = class_name return # namespace_name def _get_namespace_name(self): """ Returns the name of the context within its own namespace. """ if self.context is not None: base = self.context.namespace_name else: base = '' if len(base) > 0: namespace_name = base + '/' + self.name else: namespace_name = self.name return namespace_name #### EOF ###################################################################### apptools-4.1.0/apptools/naming/pyfs_state_factory.py0000644000175100001440000000343111674464005023742 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ State factory for Python File System contexts. """ # Enthought library imports. from apptools.io.api import File # Local imports. from address import Address from reference import Reference from state_factory import StateFactory class PyFSStateFactory(StateFactory): """ State factory for Python File System contexts. """ ########################################################################### # 'StateFactory' interface. ########################################################################### def get_state_to_bind(self, obj, name, context): """ Returns the state of an object for binding. """ state = None if isinstance(obj, File): # If the file is not actually in the directory represented by the # context then we create and bind a reference to it. if obj.parent.path != context.path: state = Reference( class_name = obj.__class__.__name__, addresses = [Address(type='file', content=obj.path)] ) return state ### EOF ####################################################################### apptools-4.1.0/apptools/naming/context_adapter_factory.py0000644000175100001440000000324711674464005024752 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The base class for all context adapter factories. """ # Enthought library imports. from apptools.type_manager.api import AdapterFactory # Local imports. from context import Context class ContextAdapterFactory(AdapterFactory): """ The base class for all context adapter factories. """ #### 'AdapterFactory' interface ########################################### # The target class (the class that the factory can adapt objects to). target_class = Context ########################################################################### # Protected 'AbstractAdapterFactory' interface. ########################################################################### def _adapt(self, adaptee, target_class, environment, context): """ Returns an adapter that adapts an object to the target class. """ adapter = self.adapter_class( adaptee=adaptee, environment=environment, context=context ) return adapter #### EOF ###################################################################### apptools-4.1.0/apptools/naming/pyfs_context_factory.py0000644000175100001440000000307711674464005024314 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Object factory for Python File System contexts. """ # Local imports. from object_factory import ObjectFactory from reference import Reference class PyFSContextFactory(ObjectFactory): """ Object factory for Python File System contexts. """ ########################################################################### # 'ObjectFactory' interface. ########################################################################### def get_object_instance(self, state, name, context): """ Creates an object using the specified state information. """ obj = None if isinstance(state, Reference): if len(state.addresses) > 0: if state.addresses[0].type == 'pyfs_context': path = state.addresses[0].content obj = context._context_factory(name, path) return obj ### EOF ####################################################################### apptools-4.1.0/apptools/naming/referenceable.py0000644000175100001440000000232411674464005022614 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Base class for classes that can produce a reference to themselves. """ # Enthought library imports. from traits.api import HasPrivateTraits, Instance # Local imports. from reference import Reference class Referenceable(HasPrivateTraits): """ Base class for classes that can produce a reference to themselves. """ #### 'Referenceable' interface ############################################ # The object's reference suitable for binding in a naming context. reference = Instance(Reference) #### EOF ###################################################################### apptools-4.1.0/apptools/naming/dir_context.py0000644000175100001440000001363311674464005022361 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The base class for all directory contexts. """ # Enthought library imports. from traits.api import Dict # Local imports. from context import Context from exception import NameNotFoundError, NotContextError class DirContext(Context): """ The base class for all directory contexts. """ # The attributes of every object in the context. The attributes for the # context itself have the empty string as the key. # # {str name : dict attributes} _attributes = Dict ########################################################################### # 'DirContext' interface. ########################################################################### def get_attributes(self, name): """ Returns the attributes associated with a named object. """ # If the name is empty then we return the attributes of this context. if len(name) == 0: attributes = self._get_attributes(name) else: # Parse the name. components = self._parse_name(name) # If there is exactly one component in the name then the operation # takes place in this context. if len(components) == 1: atom = components[0] if not self._is_bound(atom): raise NameNotFoundError(name) # Do the actual get. attributes = self._get_attributes(atom) # Otherwise, attempt to continue resolution into the next context. else: if not self._is_bound(components[0]): raise NameNotFoundError(components[0]) next_context = self._get_next_context(components[0]) attributes = next_context.get_attributes( '/'.join(components[1:])) return attributes def set_attributes(self, name, attributes): """ Sets the attributes associated with a named object. """ # If the name is empty then we set the attributes of this context. if len(name) == 0: attributes = self._set_attributes(name, attributes) else: # Parse the name. components = self._parse_name(name) # If there is exactly one component in the name then the operation # takes place in this context. if len(components) == 1: atom = components[0] if not self._is_bound(atom): raise NameNotFoundError(name) # Do the actual set. self._set_attributes(atom, attributes) # Otherwise, attempt to continue resolution into the next context. else: if not self._is_bound(components[0]): raise NameNotFoundError(components[0]) next_context = self._get_next_context(components[0]) next_context.set_attributes( '/'.join(components[1:]), attributes ) return # fixme: Non-JNDI def find_bindings(self, visitor): """ Find bindings with attributes matching criteria in visitor. Visitor is a function that is passed the bindings for each level of the heirarchy and the attribute dictionary for those bindings. The visitor examines the bindings and dictionary and returns the bindings it is interested in. """ bindings = visitor(self.list_bindings(), self._attributes) # recursively check other sub contexts. for binding in self.list_bindings(): obj = binding.obj if isinstance(obj, DirContext): bindings.extend(obj.find_bindings(visitor)) return bindings ########################################################################### # Protected 'DirContext' interface. ########################################################################### def _get_attributes(self, name): """ Returns the attributes of an object in this context. """ attributes = self._attributes.setdefault(name, {}) return attributes.copy() def _set_attributes(self, name, attributes): """ Sets the attributes of an object in this context. """ self._attributes[name] = attributes return ########################################################################### # Protected 'Context' interface. ########################################################################### def _unbind(self, name): """ Unbinds a name from this context. """ super(DirContext, self)._unbind(name) if name in self._attributes: del self._attributes[name] return def _rename(self, old_name, new_name): """ Renames an object in this context. """ super(DirContext, self)._rename(old_name, new_name) if old_name in self._attributes: self._attributes[new_name] = self._attributes[old_name] del self._attributes[old_name] return def _destroy_subcontext(self, name): """ Destroys a sub-context of this context. """ super(DirContext, self)._destroy_subcontext(name) if name in self._attributes: del self._attributes[name] return #### EOF ###################################################################### apptools-4.1.0/apptools/naming/address.py0000644000175100001440000000215011674464005021454 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The address of a commuications endpoint. """ # Enthought library imports. from traits.api import Any, HasTraits, Str class Address(HasTraits): """ The address of a communications end-point. It contains a type that describes the communication mechanism, and the actual address content. """ # The type of the address. type = Str # The actual content. content = Any #### EOF ###################################################################### apptools-4.1.0/apptools/naming/context.py0000644000175100001440000006031011674464005021515 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The base class for all naming contexts. """ # Enthought library imports. from traits.api import Any, Dict, Event, HasTraits, Instance from traits.api import Property, Str from apptools.type_manager.api import TypeManager # Local imports. from binding import Binding from exception import InvalidNameError, NameAlreadyBoundError from exception import NameNotFoundError, NotContextError from exception import OperationNotSupportedError from naming_event import NamingEvent from naming_manager import naming_manager from object_factory import ObjectFactory from state_factory import StateFactory from unique_name import make_unique_name # Constants for environment property keys. INITIAL_CONTEXT_FACTORY = "apptools.naming.factory.initial" OBJECT_FACTORIES = "apptools.naming.factory.object" STATE_FACTORIES = "apptools.naming.factory.state" # Non-JNDI. TYPE_MANAGER = "apptools.naming.factory.type.manager" # The default environment. ENVIRONMENT = { # 'Context' properties. OBJECT_FACTORIES : [], STATE_FACTORIES : [], # Non-JNDI. TYPE_MANAGER : None, } class Context(HasTraits): """ The base class for all naming contexts. """ # Keys for environment properties. INITIAL_CONTEXT_FACTORY = INITIAL_CONTEXT_FACTORY OBJECT_FACTORIES = OBJECT_FACTORIES STATE_FACTORIES = STATE_FACTORIES # Non-JNDI. TYPE_MANAGER = TYPE_MANAGER #### 'Context' interface ################################################## # The naming environment in effect for this context. environment = Dict(ENVIRONMENT) # The name of the context within its own namespace. namespace_name = Property(Str) # The type manager in the context's environment (used to create context # adapters etc.). # # fixme: This is an experimental 'convenience' trait, since it is common # to get hold of the context's type manager to see if some object has a # context adapter. type_manager = Property(Instance(TypeManager)) #### Events #### # Fired when an object has been added to the context (either via 'bind' or # 'create_subcontext'). object_added = Event(NamingEvent) # Fired when an object has been changed (via 'rebind'). object_changed = Event(NamingEvent) # Fired when an object has been removed from the context (either via # 'unbind' or 'destroy_subcontext'). object_removed = Event(NamingEvent) # Fired when an object in the context has been renamed (via 'rename'). object_renamed = Event(NamingEvent) # Fired when the contents of the context have changed dramatically. context_changed = Event(NamingEvent) #### Protected 'Context' interface ####################################### # The bindings in the context. _bindings = Dict(Str, Any) ########################################################################### # 'Context' interface. ########################################################################### #### Properties ########################################################### def _get_namespace_name(self): """ Return the name of the context within its own namespace. That is the full-path, through the namespace this context participates in, to get to this context. For example, if the root context of the namespace was called 'Foo', and there was a subcontext of that called 'Bar', and we were within that and called 'Baz', then this should return 'Foo/Bar/Baz'. """ # FIXME: We'd like to raise an exception and force implementors to # decide what to do. However, it appears to be pretty common that # most Context implementations do not override this method -- possibly # because the comments aren't clear on what this is supposed to be? # # Anyway, if we raise an exception then it is impossible to use any # evaluations when building a Traits UI for a Context. That is, the # Traits UI can't include items that have a 'visible_when' or # 'enabled_when' evaluation. This is because the Traits evaluation # code calls the 'get()' method on the Context which attempts to # retrieve the current namespace_name value. #raise OperationNotSupportedError() return '' def _get_type_manager(self): """ Returns the type manager in the context's environment. This will return None if no type manager was used to create the initial context. """ return self.environment.get(self.TYPE_MANAGER) #### Methods ############################################################## def bind(self, name, obj, make_contexts=False): """ Binds a name to an object. If 'make_contexts' is True then any missing intermediate contexts are created automatically. """ if len(name) == 0: raise InvalidNameError('empty name') # Parse the name. components = self._parse_name(name) # If there is exactly one component in the name then the operation # takes place in this context. if len(components) == 1: atom = components[0] # Is the name already bound? if self._is_bound(atom): raise NameAlreadyBoundError(name) # Do the actual bind. self._bind(atom, obj) # Trait event notification. self.object_added = NamingEvent( new_binding=Binding(name=name, obj=obj, context=self) ) # Otherwise, attempt to continue resolution into the next context. else: if not self._is_bound(components[0]): if make_contexts: self._create_subcontext(components[0]) else: raise NameNotFoundError(components[0]) next_context = self._get_next_context(components[0]) next_context.bind('/'.join(components[1:]), obj, make_contexts) return def rebind(self, name, obj, make_contexts=False): """ Binds an object to a name that may already be bound. If 'make_contexts' is True then any missing intermediate contexts are created automatically. The object may be a different object but may also be the same object that is already bound to the specified name. The name may or may not be already used. Think of this as a safer version of 'bind' since this one will never raise an exception regarding a name being used. """ if len(name) == 0: raise InvalidNameError('empty name') # Parse the name. components = self._parse_name(name) # If there is exactly one component in the name then the operation # takes place in this context. if len(components) == 1: # Do the actual rebind. self._rebind(components[0], obj) # Trait event notification. self.object_changed = NamingEvent( new_binding=Binding(name=name, obj=obj, context=self) ) # Otherwise, attempt to continue resolution into the next context. else: if not self._is_bound(components[0]): if make_contexts: self._create_subcontext(components[0]) else: raise NameNotFoundError(components[0]) next_context = self._get_next_context(components[0]) next_context.rebind('/'.join(components[1:]), obj, make_contexts) return def unbind(self, name): """ Unbinds a name. """ if len(name) == 0: raise InvalidNameError('empty name') # Parse the name. components = self._parse_name(name) # If there is exactly one component in the name then the operation # takes place in this context. if len(components) == 1: atom = components[0] if not self._is_bound(atom): raise NameNotFoundError(name) # Lookup the object that we are unbinding to use in the event # notification. obj = self._lookup(atom) # Do the actual unbind. self._unbind(atom) # Trait event notification. self.object_removed = NamingEvent( old_binding=Binding(name=name, obj=obj, context=self) ) # Otherwise, attempt to continue resolution into the next context. else: if not self._is_bound(components[0]): raise NameNotFoundError(components[0]) next_context = self._get_next_context(components[0]) next_context.unbind('/'.join(components[1:])) return def rename(self, old_name, new_name): """ Binds a new name to an object. """ if len(old_name) == 0 or len(new_name) == 0: raise InvalidNameError('empty name') # Parse the names. old_components = self._parse_name(old_name) new_components = self._parse_name(new_name) # If there is axactly one component in BOTH names then the operation # takes place ENTIRELY in this context. if len(old_components) == 1 and len(new_components) == 1: # Is the old name actually bound? if not self._is_bound(old_name): raise NameNotFoundError(old_name) # Is the new name already bound? if self._is_bound(new_name): raise NameAlreadyBoundError(new_name) # Do the actual rename. self._rename(old_name, new_name) # Lookup the object that we are renaming to use in the event # notification. obj = self._lookup(new_name) # Trait event notification. self.object_renamed = NamingEvent( old_binding=Binding(name=old_name, obj=obj, context=self), new_binding=Binding(name=new_name, obj=obj, context=self) ) else: # fixme: This really needs to be transactional in case the bind # succeeds but the unbind fails. To be safe should we just not # support cross-context renaming for now?!?! # # Lookup the object. obj = self.lookup(old_name) # Bind the new name. self.bind(new_name, obj) # Unbind the old one. self.unbind(old_name) return def lookup(self, name): """ Resolves a name relative to this context. """ # If the name is empty we return the context itself. if len(name) == 0: # fixme: The JNDI spec. says that this should return a COPY of # the context. return self # Parse the name. components = self._parse_name(name) # If there is exactly one component in the name then the operation # takes place in this context. if len(components) == 1: atom = components[0] if not self._is_bound(atom): raise NameNotFoundError(name) # Do the actual lookup. obj = self._lookup(atom) # Otherwise, attempt to continue resolution into the next context. else: if not self._is_bound(components[0]): raise NameNotFoundError(components[0]) next_context = self._get_next_context(components[0]) obj = next_context.lookup('/'.join(components[1:])) return obj # fixme: Non-JNDI def lookup_binding(self, name): """ Looks up the binding for a name relative to this context. """ if len(name) == 0: raise InvalidNameError('empty name') # Parse the name. components = self._parse_name(name) # If there is exactly one component in the name then the operation # takes place in this context. if len(components) == 1: atom = components[0] if not self._is_bound(atom): raise NameNotFoundError(name) # Do the actual lookup. binding = self._lookup_binding(atom) # Otherwise, attempt to continue resolution into the next context. else: if not self._is_bound(components[0]): raise NameNotFoundError(components[0]) next_context = self._get_next_context(components[0]) binding = next_context.lookup_binding('/'.join(components[1:])) return binding # fixme: Non-JNDI def lookup_context(self, name): """ Resolves a name relative to this context. The name MUST resolve to a context. This method is useful to return context adapters. """ # If the name is empty we return the context itself. if len(name) == 0: # fixme: The JNDI spec. says that this should return a COPY of # the context. return self # Parse the name. components = self._parse_name(name) # If there is exactly one component in the name then the operation # takes place in this context. if len(components) == 1: atom = components[0] if not self._is_bound(atom): raise NameNotFoundError(name) # Do the actual lookup. obj = self._get_next_context(atom) # Otherwise, attempt to continue resolution into the next context. else: if not self._is_bound(components[0]): raise NameNotFoundError(components[0]) next_context = self._get_next_context(components[0]) obj = next_context.lookup('/'.join(components[1:])) return obj def create_subcontext(self, name): """ Creates a sub-context. """ if len(name) == 0: raise InvalidNameError('empty name') # Parse the name. components = self._parse_name(name) # If there is exactly one component in the name then the operation # takes place in this context. if len(components) == 1: atom = components[0] # Is the name already bound? if self._is_bound(atom): raise NameAlreadyBoundError(name) # Do the actual creation of the sub-context. sub = self._create_subcontext(atom) # Trait event notification. self.object_added = NamingEvent( new_binding=Binding(name=name, obj=sub, context=self) ) # Otherwise, attempt to continue resolution into the next context. else: if not self._is_bound(components[0]): raise NameNotFoundError(components[0]) next_context = self._get_next_context(components[0]) sub = next_context.create_subcontext('/'.join(components[1:])) return sub def destroy_subcontext(self, name): """ Destroys a sub-context. """ if len(name) == 0: raise InvalidNameError('empty name') # Parse the name. components = self._parse_name(name) # If there is exactly one component in the name then the operation # takes place in this context. if len(components) == 1: atom = components[0] if not self._is_bound(atom): raise NameNotFoundError(name) obj = self._lookup(atom) if not self._is_context(atom): raise NotContextError(name) # Do the actual destruction of the sub-context. self._destroy_subcontext(atom) # Trait event notification. self.object_removed = NamingEvent( old_binding=Binding(name=name, obj=obj, context=self) ) # Otherwise, attempt to continue resolution into the next context. else: if not self._is_bound(components[0]): raise NameNotFoundError(components[0]) next_context = self._get_next_context(components[0]) next_context.destroy_subcontext('/'.join(components[1:])) return # fixme: Non-JNDI def get_unique_name(self, prefix): """ Returns a name that is unique within the context. The name returned will start with the specified prefix. """ return make_unique_name(prefix, existing=self.list_names(''), format='%s (%d)') def list_names(self, name=''): """ Lists the names bound in a context. """ # If the name is empty then the operation takes place in this context. if len(name) == 0: names = self._list_names() # Otherwise, attempt to continue resolution into the next context. else: # Parse the name. components = self._parse_name(name) if not self._is_bound(components[0]): raise NameNotFoundError(components[0]) next_context = self._get_next_context(components[0]) names = next_context.list_names('/'.join(components[1:])) return names def list_bindings(self, name=''): """ Lists the bindings in a context. """ # If the name is empty then the operation takes place in this context. if len(name) == 0: bindings = self._list_bindings() # Otherwise, attempt to continue resolution into the next context. else: # Parse the name. components = self._parse_name(name) if not self._is_bound(components[0]): raise NameNotFoundError(components[0]) next_context = self._get_next_context(components[0]) bindings = next_context.list_bindings('/'.join(components[1:])) return bindings # fixme: Non-JNDI def is_context(self, name): """ Returns True if the name is bound to a context. """ # If the name is empty then it refers to this context. if len(name) == 0: is_context = True else: # Parse the name. components = self._parse_name(name) # If there is exactly one component in the name then the operation # takes place in this context. if len(components) == 1: atom = components[0] if not self._is_bound(atom): raise NameNotFoundError(name) # Do the actual check. is_context = self._is_context(atom) # Otherwise, attempt to continue resolution into the next context. else: if not self._is_bound(components[0]): raise NameNotFoundError(components[0]) next_context = self._get_next_context(components[0]) is_context = next_context.is_context('/'.join(components[1:])) return is_context # fixme: Non-JNDI def search(self, obj): """ Returns a list of namespace names that are bound to obj. """ # don't look for None if obj is None: return [] # Obj is bound to these names relative to this context names = [] # path contain the name components down to the current context path = [] self._search( obj, names, path, {} ) return names ########################################################################### # Protected 'Context' interface. ########################################################################### def _parse_name(self, name): """ Parse a name into a list of components. e.g. 'foo/bar/baz' -> ['foo', 'bar', 'baz'] """ return name.split('/') def _is_bound(self, name): """ Is a name bound in this context? """ return name in self._bindings def _lookup(self, name): """ Looks up a name in this context. """ obj = self._bindings[name] return naming_manager.get_object_instance(obj, name, self) def _lookup_binding(self, name): """ Looks up the binding for a name in this context. """ return Binding(name=name, obj=self._lookup(name), context=self) def _bind(self, name, obj): """ Binds a name to an object in this context. """ state = naming_manager.get_state_to_bind(obj, name, self) self._bindings[name] = state return def _rebind(self, name, obj): """ Rebinds a name to an object in this context. """ self._bind(name, obj) return def _unbind(self, name): """ Unbinds a name from this context. """ del self._bindings[name] return def _rename(self, old_name, new_name): """ Renames an object in this context. """ # Bind the new name. self._bindings[new_name] = self._bindings[old_name] # Unbind the old one. del self._bindings[old_name] return def _create_subcontext(self, name): """ Creates a sub-context of this context. """ sub = self.__class__(environment=self.environment) self._bindings[name] = sub return sub def _destroy_subcontext(self, name): """ Destroys a sub-context of this context. """ del self._bindings[name] return def _list_bindings(self): """ Lists the bindings in this context. """ bindings = [] for name in self._list_names(): bindings.append( Binding(name=name, obj=self._lookup(name), context=self) ) return bindings def _list_names(self): """ Lists the names bound in this context. """ return self._bindings.keys() def _is_context(self, name): """ Returns True if a name is bound to a context. """ return self._get_next_context(name) is not None def _get_next_context(self, name): """ Returns the next context. """ obj = self._lookup(name) # If the object is a context then everything is just dandy. if isinstance(obj, Context): next_context = obj # Otherwise, instead of just giving up, see if the context has a type # manager that knows how to adapt the object to make it quack like a # context. else: next_context = self._get_context_adapter(obj) # If no adapter was found then we cannot continue name resolution. if next_context is None: raise NotContextError(name) return next_context def _search( self, obj, names, path, searched): """ Append to names any name bound to obj. Join path and name with '/' to for a complete name from the top context. """ # Check the bindings recursively. for binding in self.list_bindings(): if binding.obj is obj: path.append( binding.name ) names.append( '/'.join(path) ) path.pop() if isinstance( binding.obj, Context ) \ and not binding.obj in searched: path.append( binding.name ) searched[binding.obj] = True binding.obj._search( obj, names, path, searched ) path.pop() return ########################################################################### # Private interface. ########################################################################### def _get_context_adapter(self, obj): """ Returns a context adapter for an object. Returns None if no such adapter is available. """ if self.type_manager is not None: adapter = self.type_manager.object_as( obj, Context, environment=self.environment, context=self ) else: adapter = None return adapter #### EOF ###################################################################### apptools-4.1.0/apptools/naming/trait_defs/0000755000175100001440000000000011674464005021603 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/naming/trait_defs/naming_traits.py0000644000175100001440000001374211674464005025023 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Defines the NamingLog and NamingIndex traits # # Written by: David C. Morrill # # Date: 08/15/2005 # # (c) Copyright 2005 by Enthought, Inc. # #------------------------------------------------------------------------------- #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- import sys from traits.api \ import Trait, TraitHandler, TraitFactory from traits.trait_base \ import class_of, get_module_name from traitsui.api \ import DropEditor from apptools.naming.api \ import Binding #------------------------------------------------------------------------------- # 'NamingInstance' trait factory: #------------------------------------------------------------------------------- def NamingInstance ( klass = None, value = '', allow_none = False, **metadata ): metadata.setdefault( 'copy', 'deep' ) return Trait( value, NamingTraitHandler( klass, or_none = allow_none, module = get_module_name() ), **metadata ) NamingInstance = TraitFactory( NamingInstance ) #------------------------------------------------------------------------------- # 'NamingTraitHandler' class: #------------------------------------------------------------------------------- class NamingTraitHandler ( TraitHandler ): #--------------------------------------------------------------------------- # Initializes the object: #--------------------------------------------------------------------------- def __init__ ( self, aClass, or_none, module ): """ Initializes the object. """ self.or_none = (or_none != False) self.module = module self.aClass = aClass if (aClass is not None) \ and (not isinstance( aClass, ( basestring, type ) )): self.aClass = aClass.__class__ def validate ( self, object, name, value ): if isinstance( value, basestring ): if value == '': if self.or_none: return '' else: self.validate_failed( object, name, value ) try: value = self._get_binding_for( value ) except: self.validate_failed( object, name, value ) if isinstance(self.aClass, basestring): self.resolve_class( object, name, value ) if (isinstance( value, Binding ) and ((self.aClass is None) or isinstance( value.obj, self.aClass ))): return value.namespace_name self.validate_failed( object, name, value ) def info ( self ): aClass = self.aClass if aClass is None: result = 'path' else: if type( aClass ) is not str: aClass = aClass.__name__ result = 'path to an instance of ' + class_of( aClass ) if self.or_none is None: return result + ' or an empty string' return result def validate_failed ( self, object, name, value ): if not isinstance( value, type ): msg = 'class %s' % value.__class__.__name__ else: msg = '%s (i.e. %s)' % ( str( type( value ) )[1:-1], repr( value ) ) self.error( object, name, msg ) def get_editor ( self, trait ): if self.editor is None: from traitsui.api import DropEditor self.editor = DropEditor( klass = self.aClass, binding = True, readonly = False ) return self.editor def post_setattr ( self, object, name, value ): other = None if value != '': other = self._get_binding_for( value ).obj object.__dict__[ name + '_' ] = other def _get_binding_for ( self, value ): result = None # FIXME: The following code makes this whole component have a dependency # on envisage, and worse, assumes the use of a particular project # plugin! This is horrible and should be refactored out, possibly to # a custom sub-class of whoever needs this behavior. try: from envisage import get_application workspace = get_application().service_registry.get_service( 'envisage.project.IWorkspace' ) result = workspace.lookup_binding( value ) except ImportError: pass return result def resolve_class ( self, object, name, value ): aClass = self.find_class() if aClass is None: self.validate_failed( object, name, value ) self.aClass = aClass # fixme: The following is quite ugly, because it wants to try and fix # the trait referencing this handler to use the 'fast path' now that the # actual class has been resolved. The problem is finding the trait, # especially in the case of List(Instance('foo')), where the # object.base_trait(...) value is the List trait, not the Instance # trait, so we need to check for this and pull out the List # 'item_trait'. Obviously this does not extend well to other traits # containing nested trait references (Dict?)... trait = object.base_trait( name ) handler = trait.handler if (handler is not self) and hasattr( handler, 'item_trait' ): trait = handler.item_trait trait.validate( self.fast_validate ) def find_class ( self ): module = self.module aClass = self.aClass col = aClass.rfind( '.' ) if col >= 0: module = aClass[ : col ] aClass = aClass[ col + 1: ] theClass = getattr( sys.modules.get( module ), aClass, None ) if (theClass is None) and (col >= 0): try: theClass = getattr( __import__( module ), aClass, None ) except: pass return theClass apptools-4.1.0/apptools/naming/trait_defs/api.py0000644000175100001440000000052611674464005022731 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Define traits useful with Naming. # # Written by: David C. Morrill # # Date: 08/16/2005 # # (c) Copyright 2005 by Enthought, Inc. # #------------------------------------------------------------------------------ from naming_traits import NamingInstance apptools-4.1.0/apptools/naming/trait_defs/__init__.py0000644000175100001440000000075411674464005023722 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Define traits useful with Naming. # # Written by: David C. Morrill # # Date: 08/16/2005 # # (c) Copyright 2005 by Enthought, Inc. # #------------------------------------------------------------------------------ #------------------------------------------------------------------------------ # Imports: #------------------------------------------------------------------------------ from api import * apptools-4.1.0/apptools/naming/initial_context_factory.py0000644000175100001440000000252711674464005024763 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The base class for all initial context factories. """ # Enthought library imports. from traits.api import HasTraits # Local imports. from context import Context class InitialContextFactory(HasTraits): """ The base class for all initial context factories. """ ########################################################################### # 'InitialContextFactory' interface. ########################################################################### def get_initial_context(self, environment): """ Creates an initial context for beginning name resolution. """ return Context(environment=environment) #### EOF ###################################################################### apptools-4.1.0/apptools/naming/dynamic_context.py0000644000175100001440000001473511674464005023233 0ustar ischnellusers00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2006 by Enthought, Inc. # All rights reserved. # #----------------------------------------------------------------------------- """ Provider of a framework that dynamically determines the contents of a context at the time of interaction with the contents rather than at the time a class is written. This capability is particularly useful when the object acting as a context is part of a plug-in application -- such as Envisage. In general, this capability allows the context to be: - Extendable by contributions from somewhere other than the original code writer - Dynamic in that the elements it is composed of can change each time someone interacts with the contents of the context. It should be noted that this capability is explicitly different from contexts that look at another container to determine their contents, such as a file system context! Users of this framework contribute items to a dynamic context by adding traits to the dynamic context instance. (This addition can happen statically through the use of a Traits Category.) The trait value is the context item's value and the trait definition's metadata determines how the item is treated within the context. The support metadata is: context_name: A non-empty string Represents the name of the item within this context. This must be present for the trait to show up as a context item though the value may change over time as the item gets bound to different names. context_order: A float value Indicates the position for the item within this context. All dynamically contributed context items are sorted by ascending order of this value using the standard list sort function. is_context: A boolean value True if the item is itself a context. """ # Standardlibrary imports import logging # Local imports from binding import Binding from context import Context from exception import OperationNotSupportedError # Setup a logger for this module. logger = logging.getLogger(__name__) class DynamicContext(Context): """ A framework that dynamically determines the contents of a context at the time of interaction with the contents rather than at the time a context class is written. It should be noted that this capability is explicitly different from contexts that look at another container to determine their contents, such as a file system context! """ ########################################################################## # 'Context' interface. ########################################################################## ### protected interface ################################################## def _is_bound(self, name): """ Is a name bound in this context? """ item = self._get_contributed_context_item(name) result = item != (None, None) return result def _is_context(self, name): """ Returns True if a name is bound to a context. """ item = self._get_contributed_context_item(name) if item != (None, None): obj, trait = item result = True == trait.is_context else: result = False return result def _list_bindings(self): """ Lists the bindings in this context. """ result = [ Binding(name=n, obj=o, context=self) for n, o, t in \ self._get_contributed_context_items() ] return result def _list_names(self): """ Lists the names bound in this context. """ result = [ n for n, o, t in self._get_contributed_context_items() ] return result def _lookup(self, name): """ Looks up a name in this context. """ item = self._get_contributed_context_item(name) if item != (None, None): obj, trait = item result = obj else: result = None return result def _rename(self, old_name, new_name): """ Renames an object in this context. """ item = self._get_contributed_context_item(old_name) if item != (None, None): obj, trait = item trait.context_name = new_name else: raise ValueError('Name "%s" not in context', old_name) def _unbind(self, name): """ Unbinds a name from this context. """ # It is an error to try to unbind any contributed context items item = self._get_contributed_context_item(name) if item != (None, None): raise OperationNotSupportedError('Unable to unbind ' + \ 'built-in with name [%s]' % name) ########################################################################## # 'DynamicContext' interface. ########################################################################## ### protected interface ################################################## def _get_contributed_context_item(self, name): """ If the specified name matches a contributed context item then returns a tuple of the item's current value and trait definition (in that order.) Otherwise, returns a tuple of (None, None). """ result = (None, None) for n, o, t in self._get_contributed_context_items(): if n == name: result = (o, t) return result def _get_contributed_context_items(self): """ Returns an ordered list of items to be treated as part of our context. Each item in the list is a tuple of its name, object, and trait definition (in that order.) """ # Our traits that get treated as context items are those that declare # themselves via metadata on the trait definition. filter = { 'context_name': lambda v: v is not None and len(v) > 0 } traits = self.traits(**filter) # Sort the list of context items according to the name of the item. traits = [ (t.context_order, n, t) for n, t in traits.items() ] traits.sort() # Convert these trait definitions into a list of name and object tuples. result = [(t.context_name, getattr(self, n), t) for order, n, t \ in traits] return result ### EOF ###################################################################### apptools-4.1.0/apptools/naming/pyfs_context.py0000644000175100001440000004466711674464005022577 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A Python File System context. """ # Standard library imports. import cPickle, glob, logging, os from os.path import join, splitext # Enthought library imports. from apptools.io.api import File from traits.api import Any, Dict, Instance, Property, Str # Local imports. from address import Address from binding import Binding from context import Context from dir_context import DirContext from exception import NameNotFoundError, NotContextError from naming_event import NamingEvent from naming_manager import naming_manager from object_serializer import ObjectSerializer from pyfs_context_factory import PyFSContextFactory from pyfs_object_factory import PyFSObjectFactory from pyfs_state_factory import PyFSStateFactory from reference import Reference from referenceable import Referenceable # Setup a logger for this module. logger = logging.getLogger(__name__) # The name of the 'special' file in which we store object attributes. ATTRIBUTES_FILE = '__attributes__' # Constants for environment property keys. FILTERS = "apptools.naming.pyfs.filters" OBJECT_SERIALIZERS = "apptools.naming.pyfs.object.serializers" # The default environment. ENVIRONMENT = { #### 'Context' properties ################################################# # Object factories. Context.OBJECT_FACTORIES : [PyFSObjectFactory(), PyFSContextFactory()], # State factories. Context.STATE_FACTORIES : [PyFSStateFactory()], #### 'PyFSContext' properties ############################################# # Object serializers. OBJECT_SERIALIZERS : [ObjectSerializer()], # List of filename patterns to ignore. These patterns are passed to # 'glob.glob', so things like '*.pyc' will do what you expect. # # fixme: We should have a generalized filter mechanism here, and '.svn' # should be moved elsewhere! FILTERS : [ATTRIBUTES_FILE, '.svn'] } class PyFSContext(DirContext, Referenceable): """ A Python File System context. This context represents a directory on a local file system. """ # The name of the 'special' file in which we store object attributes. ATTRIBUTES_FILE = ATTRIBUTES_FILE # Environment property keys. FILTERS = FILTERS OBJECT_SERIALIZERS = OBJECT_SERIALIZERS #### 'Context' interface ################################################## # The naming environment in effect for this context. environment = Dict(ENVIRONMENT) # The name of the context within its own namespace. namespace_name = Property(Str) #### 'PyFSContext' interface ############################################## # The name of the context (the last component of the path). name = Str # The path name of the directory on the local file system. path = Str #### 'Referenceable' interface ############################################ # The object's reference suitable for binding in a naming context. reference = Property(Instance(Reference)) #### Private interface #################################################### # A mapping from bound name to the name of the corresponding file or # directory on the file system. _name_to_filename_map = Dict#(Str, Str) # The attributes of every object in the context. The attributes for the # context itself have the empty string as the key. # # {str name : dict attributes} # # fixme: Don't use 'Dict' here as it causes problems when pickling because # trait dicts have a reference back to the parent object (hence we end up # pickling all kinds of things that we don't need or want to!). _attributes = Any ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **traits): """ Creates a new context. """ # Base class constructor. super(PyFSContext, self).__init__(**traits) # We cache each object as it is looked up so that all accesses to a # serialized Python object return a reference to exactly the same one. self._cache = {} return ########################################################################### # 'PyFSContext' interface. ########################################################################### #### Properties ########################################################### def _get_namespace_name(self): """ Returns the name of the context within its own namespace. """ # fixme: clean this up with an initial context API! if 'root' in self.environment: root = self.environment['root'] namespace_name = self.path[len(root) + 1:] else: namespace_name = self.path # fixme: This is a bit dodgy 'cos we actually return a name that can # be looked up, and not the file system name... namespace_name = '/'.join(namespace_name.split(os.path.sep)) return namespace_name #### methods ############################################################## def refresh(self): """ Refresh the context to reflect changes in the file system. """ # fixme: This needs more work 'cos if we refresh a context then we # will load new copies of serialized Python objects! # This causes the initializer to run again the next time the trait is # accessed. self.reset_traits(['_name_to_filename_map']) # Clear out the cache. self._cache = {} # fixme: This is a bit hacky since the context in the binding may # not be None! self.context_changed = NamingEvent( new_binding=Binding(name=self.name, obj=self, context=None) ) return ########################################################################### # 'Referenceable' interface. ########################################################################### #### Properties ########################################################### def _get_reference(self): """ Returns a reference to this object suitable for binding. """ abspath = os.path.abspath(self.path) reference = Reference( class_name = self.__class__.__name__, addresses = [Address(type='pyfs_context', content=abspath)] ) return reference ########################################################################### # Protected 'Context' interface. ########################################################################### def _is_bound(self, name): """ Is a name bound in this context? """ return name in self._name_to_filename_map def _lookup(self, name): """ Looks up a name in this context. """ if name in self._cache: obj = self._cache[name] else: # Get the full path to the file. path = join(self.path, self._name_to_filename_map[name]) # If the file contains a serialized Python object then load it. for serializer in self._get_object_serializers(): if serializer.can_load(path): try: state = serializer.load(path) # If the load fails then we create a generic file resource # (the idea being that it might be useful to have access to # the file to see what went wrong). except: state = File(path) logger.exception('Error loading resource at %s' % path) break # Otherwise, it must just be a file or folder. else: # Directories are contexts. if os.path.isdir(path): state = self._context_factory(name, path) # Files are just files! elif os.path.isfile(path): state = File(path) else: raise ValueError('unrecognized file for %s' % name) # Get the actual object from the naming manager. obj = naming_manager.get_object_instance(state, name, self) # Update the cache. self._cache[name] = obj return obj def _bind(self, name, obj): """ Binds a name to an object in this context. """ # Get the actual state to bind from the naming manager. state = naming_manager.get_state_to_bind(obj, name, self) # If the object is actually an abstract file then we don't have to # do anything. if isinstance(state, File): if not state.exists: state.create_file() filename = name # Otherwise we are binding an arbitrary Python object, so find a # serializer for it. else: for serializer in self._get_object_serializers(): if serializer.can_save(obj): path = serializer.save(join(self.path, name), obj) filename = os.path.basename(path) break else: raise ValueError('cannot serialize object %s' % name) # Update the name to filename map. self._name_to_filename_map[name] = filename # Update the cache. self._cache[name] = obj return state def _rebind(self, name, obj): """ Rebinds a name to an object in this context. """ # We unbind first to make sure that the old file gets removed (this # is handy if the object that we are rebinding has a different # serializer than the current one). #self._unbind(name) self._bind(name, obj) return def _unbind(self, name): """ Unbinds a name from this context. """ # Get the full path to the file. path = join(self.path, self._name_to_filename_map[name]) # Remove it! f = File(path) f.delete() # Update the name to filename map. del self._name_to_filename_map[name] # Update the cache. if name in self._cache: del self._cache[name] # Remove any attributes. if name in self._attributes: del self._attributes[name] self._save_attributes() return def _rename(self, old_name, new_name): """ Renames an object in this context. """ # Get the old filename. old_filename = self._name_to_filename_map[old_name] old_file = File(join(self.path, old_filename)) # Lookup the object bound to the old name. This has the side effect # of adding the object to the cache under the name 'old_name'. obj = self._lookup(old_name) # We are renaming a LOCAL context (ie. a folder)... if old_file.is_folder: # Create the new filename. new_filename = new_name new_file = File(join(self.path, new_filename)) # Move the folder. old_file.move(new_file) # Update the 'Context' object. obj.path = new_file.path # Update the cache. self._cache[new_name] = obj del self._cache[old_name] # Refreshing the context makes sure that all of its contents # reflect the new name (i.e., sub-folders and files have the # correct path). # # fixme: This currently results in new copies of serialized # Python objects! We need to be a bit more judicious in the # refresh. obj.refresh() # We are renaming a file... elif isinstance(obj, File): # Create the new filename. new_filename = new_name new_file = File(join(self.path, new_filename)) # Move the file. old_file.move(new_file) # Update the 'File' object. obj.path = new_file.path # Update the cache. self._cache[new_name] = obj del self._cache[old_name] # We are renaming a serialized Python object... else: # Create the new filename. new_filename = new_name + old_file.ext new_file = File(join(self.path, new_filename)) old_file.delete() # Update the cache. if old_name in self._cache: self._cache[new_name] = self._cache[old_name] del self._cache[old_name] # Force the creation of the new file. # # fixme: I'm not sure that this is really the place for this. We # do it because often the 'name' of the object is actually an # attribute of the object itself, and hence we want the serialized # state to reflect the new name... Hmmm... self._rebind(new_name, obj) # Update the name to filename map. del self._name_to_filename_map[old_name] self._name_to_filename_map[new_name] = new_filename # Move any attributes over to the new name. if old_name in self._attributes: self._attributes[new_name] = self._attributes[old_name] del self._attributes[old_name] self._save_attributes() return def _create_subcontext(self, name): """ Creates a sub-context of this context. """ path = join(self.path, name) # Create a directory. os.mkdir(path) # Create a sub-context that represents the directory. sub = self._context_factory(name, path) # Update the name to filename map. self._name_to_filename_map[name] = name # Update the cache. self._cache[name] = sub return sub def _destroy_subcontext(self, name): """ Destroys a sub-context of this context. """ return self._unbind(name) def _list_names(self): """ Lists the names bound in this context. """ return self._name_to_filename_map.keys() # fixme: YFI this is not part of the protected 'Context' interface so # what is it doing here? def get_unique_name(self, name): ext = splitext(name)[1] # specially handle '.py' files if ext != '.py': return super(PyFSContext, self).get_unique_name(name) body = splitext(name)[0] names = self.list_names() i = 2 unique = name while unique in names: unique = body + '_' + str(i) + '.py' i += 1 return unique ########################################################################### # Protected 'DirContext' interface. ########################################################################### def _get_attributes(self, name): """ Returns the attributes of an object in this context. """ attributes = self._attributes.setdefault(name, {}) return attributes.copy() def _set_attributes(self, name, attributes): """ Sets the attributes of an object in this context. """ self._attributes[name] = attributes self._save_attributes() return ########################################################################### # Private interface. ########################################################################### def _get_filters(self): """ Returns the filters for this context. """ return self.environment.get(self.FILTERS, []) def _get_object_serializers(self): """ Returns the object serializers for this context. """ return self.environment.get(self.OBJECT_SERIALIZERS, []) def _context_factory(self, name, path): """ Create a sub-context. """ return self.__class__(path=path, environment=self.environment) def _save_attributes(self): """ Saves all attributes to the attributes file. """ path = join(self.path, self.ATTRIBUTES_FILE) f = file(path, 'wb') cPickle.dump(self._attributes, f, 1) f.close() return #### Trait initializers ################################################### def __name_to_filename_map_default(self): """ Initializes the '_name_to_filename' trait. """ # fixme: We should have a generalized filter mechanism (instead of # just 'glob' patterns we should have filter objects that can be a bit # more flexible in how they do the filtering). patterns = [join(self.path, filter) for filter in self._get_filters()] name_to_filename_map = {} for filename in os.listdir(self.path): path = join(self.path, filename) for pattern in patterns: if path in glob.glob(pattern): break else: for serializer in self._get_object_serializers(): if serializer.can_load(filename): # fixme: We should probably get the name from the # serializer instead of assuming that we can just # drop the file exension. name, ext = os.path.splitext(filename) break else: name = filename name_to_filename_map[name] = filename return name_to_filename_map def __attributes_default(self): """ Initializes the '_attributes' trait. """ attributes_file = File(join(self.path, self.ATTRIBUTES_FILE)) if attributes_file.is_file: f = file(attributes_file.path, 'rb') attributes = cPickle.load(f) f.close() else: attributes = {} return attributes #### Trait event handlers ################################################# def _path_changed(self): """ Called when the context's path has changed. """ basename = os.path.basename(self.path) self.name, ext = os.path.splitext(basename) return #### EOF ###################################################################### apptools-4.1.0/apptools/naming/naming_manager.py0000644000175100001440000000576111674464005023005 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The naming manager. """ # Enthought library imports. from traits.api import HasTraits class NamingManager(HasTraits): """ The naming manager. """ ########################################################################### # 'NamingManager' interface. ########################################################################### def get_state_to_bind(self, obj, name, context): """ Returns the state of an object for binding. The naming manager asks the context for its list of STATE factories and then calls them one by one until it gets a non-None result indicating that the factory recognised the object and created state information for it. If none of the factories recognize the object (or if the context has no factories) then the object itself is returned. """ # Local imports. from context import Context # We get the state factories from the context's environment. state_factories = context.environment[Context.STATE_FACTORIES] for state_factory in state_factories: state = state_factory.get_state_to_bind(obj, name, context) if state is not None: break else: state = obj return state def get_object_instance(self, info, name, context): """ Creates an object using the specified state information. The naming manager asks the context for its list of OBJECT factories and calls them one by one until it gets a non-None result, indicating that the factory recognised the information and created an object. If none of the factories recognize the state information (or if the context has no factories) then the state information itself is returned. """ # Local imports. from context import Context # We get the object factories from the context's environment. object_factories = context.environment[Context.OBJECT_FACTORIES] for object_factory in object_factories: obj = object_factory.get_object_instance(info, name, context) if obj is not None: break else: obj = info return obj # Singleton instance. naming_manager = NamingManager() ### EOF ####################################################################### apptools-4.1.0/apptools/naming/state_factory.py0000644000175100001440000000302111674464005022674 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The base class for all state factories. """ # Enthought library imports. from traits.api import HasPrivateTraits class StateFactory(HasPrivateTraits): """ The base class for all state factories. A state factory accepts an object and returns some data representing the object that is suitable for storing in a particular context. """ ########################################################################### # 'StateFactory' interface. ########################################################################### def get_state_to_bind(self, obj, name, context): """ Returns the state of an object for binding. Returns None if the factory cannot create the state (ie. it does not recognise the object passed to it). """ raise NotImplementedError ### EOF ####################################################################### apptools-4.1.0/apptools/naming/exception.py0000644000175100001440000000342611674464005022034 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Naming exceptions. """ class NamingError(Exception): """ Base class for all naming exceptions. """ class InvalidNameError(NamingError): """ Invalid name. This exception is thrown when the name passed to a naming operation does not conform to the syntax of the naming system (or is empty etc). """ class NameAlreadyBoundError(NamingError): """ Name already bound. This exception is thrown when an attempt is made to bind a name that is already bound in the current context. """ class NameNotFoundError(NamingError): """ Name not found. This exception is thrown when a component of a name cannot be resolved because it is not bound in the current context. """ class NotContextError(NamingError): """ Not a context. This exception is thrown when a naming operation has reached a point where a context is required to continue the operation, but the resolved object is not a context. """ class OperationNotSupportedError(NamingError): """ The context does support the requested operation. """ #### EOF ###################################################################### apptools-4.1.0/apptools/naming/object_serializer.py0000644000175100001440000000560711674464005023540 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The base class for all object serializers. """ # Standard library imports. import logging from traceback import print_exc from os.path import splitext #import cPickle #import pickle # Enthought library imports. import apptools.sweet_pickle as sweet_pickle from traits.api import HasTraits, Str # Setup a logger for this module. logger = logging.getLogger(__name__) class ObjectSerializer(HasTraits): """ The base class for all object serializers. """ #### 'ObjectSerializer' interface ######################################### # The file extension recognized by this serializer. ext = Str('.pickle') ########################################################################### # 'ObjectSerializer' interface. ########################################################################### def can_load(self, path): """ Returns True if the serializer can load a file. """ rest, ext = splitext(path) return ext == self.ext def load(self, path): """ Loads an object from a file. """ # Unpickle the object. f = file(path, 'rb') try: try: obj = sweet_pickle.load(f) # obj = cPickle.load(f) # obj = pickle.load(f) except Exception, ex: print_exc() logger.exception( "Failed to load pickle file: %s, %s" % (path, ex)) raise finally: f.close() return obj def can_save(self, obj): """ Returns True if the serializer can save an object. """ return True def save(self, path, obj): """ Saves an object to a file. """ if not path.endswith(self.ext): actual_path = path + self.ext else: actual_path = path # Pickle the object. f = file(actual_path, 'wb') try: sweet_pickle.dump(obj, f, 1) # cPickle.dump(obj, f, 1) # pickle.dump(obj, f, 1) except Exception, ex: logger.exception( "Failed to pickle into file: %s, %s, object:%s" % (path, ex, obj)) print_exc() f.close() return actual_path ### EOF ####################################################################### apptools-4.1.0/apptools/naming/object_factory.py0000644000175100001440000000303111674464005023023 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The base class for all object factories. """ # Enthought library imports. from traits.api import HasTraits class ObjectFactory(HasTraits): """ The base class for all object factories. An object factory accepts some information about how to create an object (such as a reference) and returns an instance of that object. """ ########################################################################### # 'ObjectFactory' interface. ########################################################################### def get_object_instance(self, state, name, context): """ Creates an object using the specified state information. Returns None if the factory cannot create the object (ie. it does not recognise the state passed to it). """ raise NotImplementedError ### EOF ####################################################################### apptools-4.1.0/apptools/naming/__init__.py0000644000175100001440000000145111674464005021571 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Manages naming contexts. Supports non-string data types and scoped preferences. Part of the AppTools project of the Enthought Tool Suite. """ from api import * apptools-4.1.0/apptools/naming/adapter/0000755000175100001440000000000011674464005021077 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/naming/adapter/list_context_adapter_factory.py0000644000175100001440000000207511674464005027423 0ustar ischnellusers00000000000000""" Context adapter factory for Python lists. """ # Enthought library imports. from apptools.naming.api import ContextAdapterFactory # Local imports. from list_context_adapter import ListContextAdapter class ListContextAdapterFactory(ContextAdapterFactory): """ Context adapter factory for Python lists. """ #### 'ContextAdapterFactory' interface #################################### # The type of object that we adapt. adaptee_class = list ########################################################################### # Protected 'AbstractAdapterFactory' interface. ########################################################################### def _adapt(self, adaptee, target_class, environment, context): """ Returns an adapter that adapts an object to the target class. """ adapter = ListContextAdapter( adaptee = adaptee, environment = environment, context = context ) return adapter #### EOF ###################################################################### apptools-4.1.0/apptools/naming/adapter/instance_context_adapter_factory.py0000644000175100001440000000351711674464005030256 0ustar ischnellusers00000000000000""" Context adapter factory for Python instances. """ # Enthought library imports. from apptools.naming.api import ContextAdapterFactory from traits.api import List, Str from apptools.type_manager import PythonObject # Local imports. from instance_context_adapter import InstanceContextAdapter class InstanceContextAdapterFactory(ContextAdapterFactory): """ Context adapter factoryfor Python instances. """ #### 'ContextAdapterFactory' interface #################################### # The type of object that we adapt. adaptee_class = PythonObject #### 'InstanceContextAdapterFactory' interface ############################ # By default every public attribute of an instance is exposed. Use the # following traits to either include or exclude attributes as appropriate. # # Regular expressions that describe the names of attributes to include. include = List(Str) # Regular expressions that describe the names of attributes to exclude. By # default we exclude 'protected' and 'private' attributes and any # attributes that are artifacts of the traits mechanism. exclude = List(Str, ['_', 'trait_']) ########################################################################### # Protected 'AbstractAdapterFactory' interface. ########################################################################### def _adapt(self, adaptee, target_class, environment, context): """ Returns an adapter that adapts an object to the target class. """ adapter = InstanceContextAdapter( adaptee = adaptee, environment = environment, context = context, include = self.include, exclude = self.exclude ) return adapter #### EOF ###################################################################### apptools-4.1.0/apptools/naming/adapter/tuple_context_adapter.py0000644000175100001440000000075311674464005026053 0ustar ischnellusers00000000000000""" Context adapter for Python tuples. """ # Enthought library imports. from traits.api import Tuple # Local imports. from list_context_adapter import ListContextAdapter class TupleContextAdapter(ListContextAdapter): """ Context adapter for Python tuples. """ #### 'ContextAdapter' interface ########################################### # The object that we are adapting. adaptee = Tuple #### EOF ###################################################################### apptools-4.1.0/apptools/naming/adapter/instance_context_adapter.py0000644000175100001440000001247111674464005026526 0ustar ischnellusers00000000000000""" Context adapter for Python instances. """ # Standard library imports. import re # Enthought library imports. from apptools.naming.api import Binding, ContextAdapter from apptools.naming.api import OperationNotSupportedError, naming_manager from traits.api import HasTraits, List, Property, Str class InstanceContextAdapter(ContextAdapter): """ Context adapter for Python instances. """ #### 'Context' interface ################################################## # The name of the context within its own namespace. namespace_name = Property(Str) #### 'InstanceContextAdapter' interface ################################### # By default every public attribute of an instance is exposed. Use the # following traits to either include or exclude attributes as appropriate. # # Regular expressions that describe the names of attributes to include. include = List(Str) # Regular expressions that describe the names of attributes to exclude. By # default we exclude 'protected' and 'private' attributes and any # attributes that are artifacts of the traits mechanism. exclude = List(Str, ['_', 'trait_']) ########################################################################### # 'Context' interface. ########################################################################### #### Properties ########################################################### def _get_namespace_name(self): """ Returns the name of the context within its own namespace. """ base = self.context.namespace_name if len(base) > 0: base += '/' names = self.context.search(self.adaptee) return base + names[0] ########################################################################### # Protected 'Context' interface. ########################################################################### def _is_bound(self, name): """ Is a name bound in this context? """ return name in self._list_names() def _lookup(self, name): """ Looks up a name in this context. """ obj = getattr(self.adaptee, name) return naming_manager.get_object_instance(obj, name, self) def _lookup_binding(self, name): """ Looks up the binding for a name in this context. """ return Binding(name=name, obj=self._lookup(name), context=self) def _bind(self, name, obj): """ Binds a name to an object in this context. """ state = naming_manager.get_state_to_bind(obj, name, self) setattr(self.adaptee, name, state) return def _rebind(self, name, obj): """ Rebinds a name to an object in this context. """ self._bind(name, obj) return def _unbind(self, name): """ Unbinds a name from this context. """ delattr(self.adaptee, name) return def _rename(self, old_name, new_name): """ Renames an object in this context. """ # Bind the new name. setattr(self.adaptee, new_name, self._lookup(old_name)) # Unbind the old one. delattr(self.adaptee, old_name) return def _create_subcontext(self, name): """ Creates a sub-context of this context. """ raise OperationNotSupportedError() def _destroy_subcontext(self, name): """ Destroys a sub-context of this context. """ raise OperationNotSupportedError() def _list_bindings(self): """ Lists the bindings in this context. """ bindings = [] for name in self._list_names(): try: obj = self._lookup(name) bindings.append(Binding(name=name, obj=obj, context=self)) # We get attribute errors when we try to look up Event traits (they # are write-only). except AttributeError: pass return bindings def _list_names(self): """ Lists the names bound in this context. """ return self._get_public_attribute_names(self.adaptee) ########################################################################### # Private interface. ########################################################################### def _get_public_attribute_names(self, obj): """ Returns the names of an object's public attributes. """ if isinstance(obj, HasTraits): names = obj.trait_names() elif hasattr(obj, '__dict__'): names = self.adaptee.__dict__.keys() else: names = [] return [name for name in names if self._is_exposed(name)] def _is_exposed(self, name): """ Returns True iff a name should be exposed. """ if len(self.include) > 0: is_exposed = self._matches(self.include, name) elif len(self.exclude) > 0: is_exposed = not self._matches(self.exclude, name) else: is_exposed = True return is_exposed def _matches(self, expressions, name): """ Returns True iff a name matches any of a list of expressions. """ for expression in expressions: if re.match(expression, name) is not None: matches = True break else: matches = False return matches #### EOF ###################################################################### apptools-4.1.0/apptools/naming/adapter/tuple_context_adapter_factory.py0000644000175100001440000000210211674464005027570 0ustar ischnellusers00000000000000""" Context adapter factory for Python tuple. """ # Enthought library imports. from apptools.naming.api import ContextAdapterFactory # Local imports. from tuple_context_adapter import TupleContextAdapter class TupleContextAdapterFactory(ContextAdapterFactory): """ Context adapter factoryfor Python tuples. """ #### 'ContextAdapterFactory' interface #################################### # The type of object that we adapt. adaptee_class = tuple ########################################################################### # Protected 'AbstractAdapterFactory' interface. ########################################################################### def _adapt(self, adaptee, target_class, environment, context): """ Returns an adapter that adapts an object to the target class. """ adapter = TupleContextAdapter( adaptee = adaptee, environment = environment, context = context ) return adapter #### EOF ###################################################################### apptools-4.1.0/apptools/naming/adapter/dict_context_adapter.py0000644000175100001440000000564411674464005025651 0ustar ischnellusers00000000000000""" Context adapter for Python dictionaries. """ # Enthought library imports. from apptools.naming.api import Binding, ContextAdapter, naming_manager class DictContextAdapter(ContextAdapter): """ Context adapter for Python dictionaries. """ #### 'ContextAdapter' interface ########################################### # The object that we are adapting. # # # fixme: We would like to specialize the 'adaptee' trait here, but if we # make it of type 'Dict' then, on assignment, traits will create a *copy* # of the actual dict which I think you'll agree is not very adapter-like! ## adaptee = Dict ########################################################################### # Protected 'Context' interface. ########################################################################### def _is_bound(self, name): """ Is a name bound in this context? """ return name in self.adaptee def _lookup(self, name): """ Looks up a name in this context. """ obj = self.adaptee[name] return naming_manager.get_object_instance(obj, name, self) def _lookup_binding(self, name): """ Looks up the binding for a name in this context. """ return Binding(name=name, obj=self._lookup(name), context=self) def _bind(self, name, obj): """ Binds a name to an object in this context. """ state = naming_manager.get_state_to_bind(obj, name, self) self.adaptee[name] = state return def _rebind(self, name, obj): """ Rebinds a name to an object in this context. """ self._bind(name, obj) return def _unbind(self, name): """ Unbinds a name from this context. """ del self.adaptee[name] return def _rename(self, old_name, new_name): """ Renames an object in this context. """ # Bind the new name. self._bind(new_name, self._lookup(old_name)) # Unbind the old one. self._unbind(old_name) return def _create_subcontext(self, name): """ Creates a sub-context of this context. """ # Create a dictionary of the same type as the one we are adapting. sub = type(self.adaptee)() self.adaptee[name] = sub return DictContextAdapter(adaptee=sub) def _destroy_subcontext(self, name): """ Destroys a sub-context of this context. """ del self.adaptee[name] return def _list_bindings(self): """ Lists the bindings in this context. """ bindings = [] for key in self.adaptee: bindings.append( Binding(name=str(key), obj=self._lookup(key), context=self) ) return bindings def _list_names(self): """ Lists the names bound in this context. """ return [str(key) for key in self.adaptee] #### EOF ###################################################################### apptools-4.1.0/apptools/naming/adapter/trait_dict_context_adapter_factory.py0000644000175100001440000000253611674464005030600 0ustar ischnellusers00000000000000""" Context adapter factory for trait dicts. """ # Enthought library imports. from apptools.naming.api import ContextAdapterFactory from traits.api import Str, TraitDict # Local imports. from trait_dict_context_adapter import TraitDictContextAdapter class TraitDictContextAdapterFactory(ContextAdapterFactory): """ Context adapter factoryfor Python trait dicts. """ #### 'ContextAdapterFactory' interface #################################### # The type of object that we adapt. adaptee_class = TraitDict #### 'TraitDictContextAdapterFactory' interface ########################### # The name of the trait (on the adaptee) that provides the trait dict. trait_name = Str ########################################################################### # Protected 'AbstractAdapterFactory' interface. ########################################################################### def _adapt(self, adaptee, target_class, environment, context): """ Returns an adapter that adapts an object to the target class. """ adapter = TraitDictContextAdapter( adaptee = adaptee, environment = environment, context = context, trait_name = self.trait_name ) return adapter #### EOF ###################################################################### apptools-4.1.0/apptools/naming/adapter/api.py0000644000175100001440000000127511674464005022227 0ustar ischnellusers00000000000000from dict_context_adapter import DictContextAdapter from dict_context_adapter_factory import DictContextAdapterFactory from instance_context_adapter import InstanceContextAdapter from instance_context_adapter_factory import InstanceContextAdapterFactory from list_context_adapter import ListContextAdapter from list_context_adapter_factory import ListContextAdapterFactory from trait_list_context_adapter import TraitListContextAdapter from trait_list_context_adapter_factory import TraitListContextAdapterFactory from tuple_context_adapter import TupleContextAdapter from tuple_context_adapter_factory import TupleContextAdapterFactory from trait_dict_context_adapter import TraitDictContextAdapter apptools-4.1.0/apptools/naming/adapter/list_context_adapter.py0000644000175100001440000001172511674464005025676 0ustar ischnellusers00000000000000""" Context adapter for Python lists. """ # Enthought library imports. from apptools.naming.api import Binding, ContextAdapter, naming_manager from traits.api import List, Property class ListContextAdapter(ContextAdapter): """ Context adapter for Python lists. """ #### 'ContextAdapter' interface ########################################### # The object that we are adapting. # # fixme: We would like to specialize the 'adaptee' trait here, but if we # make it of type 'List' then, on assignment, traits will create a *copy* # of the actual list which I think you'll agree is not very adapter-like! ## adaptee = List #### 'ListContextAdapter' interface ####################################### # The list that we are adapting. collection = Property(List) ########################################################################### # Protected 'Context' interface. ########################################################################### def _is_bound(self, name): """ Is a name bound in this context? """ return name in self._list_names() def _lookup(self, name): """ Looks up a name in this context. """ binding = self._get_binding_with_name(name) return naming_manager.get_object_instance(binding.obj, name, self) def _bind(self, name, obj): """ Binds a name to an object in this context. """ state = naming_manager.get_state_to_bind(obj, name, self) self.collection.append(state) return def _rebind(self, name, obj): """ Rebinds a name to an object in this context. """ index = 0 for binding in self.list_bindings(''): if binding.name == name: self.collection[index] = obj break index = index + 1 # The name is not already bound. else: self._bind(name, obj) return def _unbind(self, name): """ Unbinds a name from this context. """ index = 0 for binding in self.list_bindings(''): if binding.name == name: del self.collection[index] break index = index + 1 else: raise SystemError('no binding with name %s' % name) return def _rename(self, old_name, new_name): """ Renames an object in this context. """ binding = self._get_binding_with_name(old_name) self._set_name(binding.obj, new_name) return def _create_subcontext(self, name): """ Creates a sub-context of this context. """ raise OperationNotSupportedError() def _destroy_subcontext(self, name): """ Destroys a sub-context of this context. """ raise OperationNotSupportedError() def _list_bindings(self): """ Lists the bindings in this context. """ bindings = [] for obj in self.collection: # Bindings have to have a string name. name = self._get_name(obj) # Create the binding. bindings.append(Binding(name=name, obj=obj, context=self)) return bindings def _list_names(self): """ Lists the names bound in this context. """ return [self._get_name(obj) for obj in self.collection] ########################################################################### # Protected 'ListContext' interface. ########################################################################### def _get_collection(self): """ Returns the collection that we are adapting. """ return self.adaptee ########################################################################### # Private interface. ########################################################################### # fixme: Allow an item name trait to be specified instead of guessing at # 'name' or 'id'! def _get_name(self, obj): """ Returns the name of an object. """ if hasattr(obj, 'name'): name = str(obj.name) elif hasattr(obj, 'id'): name = str(obj.id) else: name = str(obj) return name def _set_name(self, obj, name): """ Sets the name of an object. """ if hasattr(obj, 'name'): obj.name = name elif hasattr(obj, 'id'): obj.id = name return def _get_binding_with_name(self, name): """ Returns the binding with the specified name. """ for binding in self.list_bindings(''): if binding.name == name: break # The reason that this is a system error and not just a naming error # is that this method is only called from inside the protected # 'Context' interface when we have already determined that the name # is bound else: raise SystemError('no binding with name %s' % name) return binding #### EOF ###################################################################### apptools-4.1.0/apptools/naming/adapter/trait_list_context_adapter.py0000644000175100001440000000415711674464005027102 0ustar ischnellusers00000000000000""" Context adapter for trait lists. """ # Enthought library imports. from traits.api import Any, List, Property, Str # Local imports. from list_context_adapter import ListContextAdapter class TraitListContextAdapter(ListContextAdapter): """ Context adapter for trait lists. """ #### 'Context' interface ################################################## # The name of the context within its own namespace. namespace_name = Property(Str) #### 'ContextAdapter' interface ########################################### # The object that we are adapting. adaptee = Any #### 'ListContextAdapter' interface ####################################### # The list that we are adapting. collection = Property(List) #### 'TraitListContextAdapter' interface ################################## # The name of the object's trait that provides the list. trait_name = Str ########################################################################### # 'Context' interface. ########################################################################### def _get_namespace_name(self): """ Returns the name of the context within its own namespace. """ return self.context.namespace_name + '/' + self.trait_name ########################################################################### # Protected 'ListContext' interface. ########################################################################### #### 'Properties' ######################################################### def _get_collection(self): """ Returns the collection that we are adapting. """ components = self.trait_name.split('.') if len(components) == 1: collection = getattr(self.adaptee, self.trait_name) else: # Find the object that contains the trait. obj = self.adaptee for component in components[:-1]: obj = getattr(obj, component) collection = getattr(obj, components[-1]) return collection #### EOF ###################################################################### apptools-4.1.0/apptools/naming/adapter/__init__.py0000644000175100001440000000002211674464005023202 0ustar ischnellusers00000000000000from api import * apptools-4.1.0/apptools/naming/adapter/dict_context_adapter_factory.py0000644000175100001440000000211311674464005027364 0ustar ischnellusers00000000000000""" Context adapter factory for Python dictionaries. """ # Enthought library imports. from apptools.naming.api import ContextAdapterFactory # Local imports. from dict_context_adapter import DictContextAdapter class DictContextAdapterFactory(ContextAdapterFactory): """ Context adapter factory for Python dictionaries. """ #### 'ContextAdapterFactory' interface #################################### # The type of object that we adapt. adaptee_class = dict ########################################################################### # Protected 'AbstractAdapterFactory' interface. ########################################################################### def _adapt(self, adaptee, target_class, environment, context): """ Returns an adapter that adapts an object to the target class. """ adapter = DictContextAdapter( adaptee = adaptee, environment = environment, context = context ) return adapter #### EOF ###################################################################### apptools-4.1.0/apptools/naming/adapter/trait_list_context_adapter_factory.py0000644000175100001440000000253611674464005030630 0ustar ischnellusers00000000000000""" Context adapter factory for trait lists. """ # Enthought library imports. from apptools.naming.api import ContextAdapterFactory from traits.api import Str, TraitList # Local imports. from trait_list_context_adapter import TraitListContextAdapter class TraitListContextAdapterFactory(ContextAdapterFactory): """ Context adapter factoryfor Python trait lists. """ #### 'ContextAdapterFactory' interface #################################### # The type of object that we adapt. adaptee_class = TraitList #### 'TraitListContextAdapterFactory' interface ########################### # The name of the trait (on the adaptee) that provides the trait list. trait_name = Str ########################################################################### # Protected 'AbstractAdapterFactory' interface. ########################################################################### def _adapt(self, adaptee, target_class, environment, context): """ Returns an adapter that adapts an object to the target class. """ adapter = TraitListContextAdapter( adaptee = adaptee, environment = environment, context = context, trait_name = self.trait_name ) return adapter #### EOF ###################################################################### apptools-4.1.0/apptools/naming/adapter/trait_dict_context_adapter.py0000644000175100001440000000231311674464005027042 0ustar ischnellusers00000000000000""" Context adapter for trait dictionaries. """ # Enthought library imports. from traits.api import Dict, Property, Str # Local imports. from dict_context_adapter import DictContextAdapter class TraitDictContextAdapter(DictContextAdapter): """ Context adapter for trait dictionaries. """ #### 'Context' interface ################################################## # The name of the context within its own namespace. namespace_name = Property(Str) #### 'ContextAdapter' interface ########################################### # The object that we are adapting. adaptee = Dict #### 'TraitDictContextAdapter' interface ################################## # The name of the object's trait that provides the dictionary. trait_name = Str ########################################################################### # 'Context' interface. ########################################################################### def _get_namespace_name(self): """ Returns the name of the context within its own namespace. """ return self.context.namespace_name + '/' + self.trait_name #### EOF ###################################################################### apptools-4.1.0/apptools/naming/py_context.py0000644000175100001440000001453211674464005022232 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A naming context for a Python namespace. """ # Enthought library imports. from traits.api import Any, Dict, Instance, Property # Local imports. from address import Address from binding import Binding from context import Context from naming_manager import naming_manager from py_object_factory import PyObjectFactory from reference import Reference from referenceable import Referenceable from referenceable_state_factory import ReferenceableStateFactory # The default environment. ENVIRONMENT = { # 'Context' properties. Context.OBJECT_FACTORIES : [PyObjectFactory()], Context.STATE_FACTORIES : [ReferenceableStateFactory()], } class PyContext(Context, Referenceable): """ A naming context for a Python namespace. """ #### 'Context' interface ################################################## # The naming environment in effect for this context. environment = Dict(ENVIRONMENT) #### 'PyContext' interface ################################################ # The Python namespace that we represent. namespace = Any # If the namespace is actual a Python object that has a '__dict__' # attribute, then this will be that object (the namespace will be the # object's '__dict__'. obj = Any #### 'Referenceable' interface ############################################ # The object's reference suitable for binding in a naming context. reference = Property(Instance(Reference)) ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **traits): """ Creates a new context. """ # Base class constructor. super(PyContext, self).__init__(**traits) if type(self.namespace) is not dict: if hasattr(self.namespace, '__dict__'): self.obj = self.namespace self.namespace = self.namespace.__dict__ else: raise ValueError('Need a dictionary or a __dict__ attribute') return ########################################################################### # 'Referenceable' interface. ########################################################################### #### Properties ########################################################### def _get_reference(self): """ Returns a reference to this object suitable for binding. """ reference = Reference( class_name = self.__class__.__name__, addresses = [Address(type='py_context', content=self.namespace)] ) return reference ########################################################################### # Protected 'Context' interface. ########################################################################### def _is_bound(self, name): """ Is a name bound in this context? """ return self.namespace.has_key(name) def _lookup(self, name): """ Looks up a name in this context. """ obj = self.namespace[name] return naming_manager.get_object_instance(obj, name, self) def _bind(self, name, obj): """ Binds a name to an object in this context. """ state = naming_manager.get_state_to_bind(obj, name,self) self.namespace[name] = state # Trait event notification. # An "added" event is fired by the bind method of the base calss (which calls # this one), so we don't need to do the changed here (which would be the wrong # thing anyway) -- LGV # # self.trait_property_changed('context_changed', None, None) return def _rebind(self, name, obj): """ Rebinds a name to a object in this context. """ self._bind(name, obj) return def _unbind(self, name): """ Unbinds a name from this context. """ del self.namespace[name] # Trait event notification. self.trait_property_changed('context_changed', None, None) return def _rename(self, old_name, new_name): """ Renames an object in this context. """ state = self.namespace[old_name] # Bind the new name. self.namespace[new_name] = state # Unbind the old one. del self.namespace[old_name] # Trait event notification. self.context_changed = True return def _create_subcontext(self, name): """ Creates a sub-context of this context. """ sub = self._context_factory(name, {}) self.namespace[name] = sub # Trait event notification. self.trait_property_changed('context_changed', None, None) return sub def _destroy_subcontext(self, name): """ Destroys a sub-context of this context. """ del self.namespace[name] # Trait event notification. self.trait_property_changed('context_changed', None, None) return def _list_bindings(self): """ Lists the bindings in this context. """ bindings = [] for name, value in self.namespace.items(): bindings.append(Binding(name=name, obj=self._lookup(name), context=self)) return bindings def _list_names(self): """ Lists the names bound in this context. """ return self.namespace.keys() ########################################################################### # Private interface. ########################################################################### def _context_factory(self, name, namespace): """ Create a sub-context. """ return self.__class__(namespace=namespace) #### EOF ###################################################################### apptools-4.1.0/apptools/naming/pyfs_object_factory.py0000644000175100001440000000304111674464005024065 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Object factory for Python File System contexts. """ # Enthought library imports. from apptools.io.api import File # Local imports. from object_factory import ObjectFactory from reference import Reference class PyFSObjectFactory(ObjectFactory): """ Object factory for Python File System contexts. """ ########################################################################### # 'ObjectFactory' interface. ########################################################################### def get_object_instance(self, state, name, context): """ Creates an object using the specified state information. """ obj = None if isinstance(state, Reference): if state.class_name == 'File' and len(state.addresses) > 0: obj = File(state.addresses[0].content) return obj ### EOF ####################################################################### apptools-4.1.0/apptools/naming/context_adapter.py0000644000175100001440000000226411674464005023221 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The base class for all context adapters. """ # Enthought library imports. from traits.api import Any, Dict, Instance, Property, Str # Local imports. from context import Context class ContextAdapter(Context): """ The base class for all context adapters. """ #### 'ContextAdapter' interface ########################################### # The object that we are adapting. adaptee = Any # The context that the object is in. context = Instance(Context) #### EOF ###################################################################### apptools-4.1.0/apptools/undo/0000755000175100001440000000000011674464005017153 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/undo/undo_manager.py0000644000175100001440000000762111674464005022172 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import Bool, Event, HasTraits, implements, \ Instance, Int, Property, Unicode # Local imports. from i_undo_manager import IUndoManager class UndoManager(HasTraits): """ The UndoManager class is the default implementation of the IUndoManager interface. """ implements(IUndoManager) #### 'IUndoManager' interface ############################################# # This is the currently active command stack and may be None. Typically it # is set when some sort of editor becomes active. active_stack = Instance('apptools.undo.api.ICommandStack') # This reflects the clean state of the currently active command stack. It # is intended to support a "document modified" indicator in the GUI. It is # maintained by the undo manager. active_stack_clean = Property(Bool) # This is the name of the command that can be redone. It will be empty if # there is no command that can be redone. It is maintained by the undo # manager. redo_name = Property(Unicode) # This is the sequence number of the next command to be performed. It is # incremented immediately before a command is invoked (by its 'do()' # method). sequence_nr = Int # This event is fired when the index of a command stack changes. The value # of the event is the stack that has changed. Note that it may not be the # active stack. stack_updated = Event # This is the name of the command that can be undone. It will be empty if # there is no command that can be undone. It is maintained by the undo # manager. undo_name = Property(Unicode) ########################################################################### # 'IUndoManager' interface. ########################################################################### def redo(self): """ Redo the last undone command of the active command stack. """ if self.active_stack is not None: self.active_stack.redo() def undo(self): """ Undo the last command of the active command stack. """ if self.active_stack is not None: self.active_stack.undo() ########################################################################### # Private interface. ########################################################################### def _active_stack_changed(self, new): """ Handle a different stack becoming active. """ # Pretend that the stack contents have changed. self.stack_updated = new def _get_active_stack_clean(self): """ Get the current clean state. """ if self.active_stack is None: active_stack_clean = True else: active_stack_clean = self.active_stack.clean return active_stack_clean def _get_redo_name(self): """ Get the current redo name. """ if self.active_stack is None: redo_name = "" else: redo_name = self.active_stack.redo_name return redo_name def _get_undo_name(self): """ Get the current undo name. """ if self.active_stack is None: undo_name = "" else: undo_name = self.active_stack.undo_name return undo_name apptools-4.1.0/apptools/undo/i_command.py0000644000175100001440000000540211674464005021454 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import Any, Interface, Unicode class ICommand(Interface): """ The command interface. The state of the data can be changed by passing an instance that implements this interface to the 'push()' method of a command stack along with any arguments. """ #### 'ICommand' interface ################################################# # This is the data on which the command operates. data = Any # This is the name of the command as it will appear in any GUI element. It # may include '&' which will be automatically removed whenever it is # inappropriate. name = Unicode ########################################################################### # 'ICommand' interface. ########################################################################### def do(self): """ This is called by the command stack to do the command and to return any value. The command must save any state necessary for the 'redo()' and 'undo()' methods to work. The class's __init__() must also ensure that deep copies of any arguments are made if appropriate. It is guaranteed that this will only ever be called once and that it will be called before any call to 'redo()' or 'undo()'. """ def merge(self, other): """ This is called by the command stack to try and merge another command with this one. True is returned if the commands were merged. 'other' is the command that is about to be executed. If the commands are merged then 'other' will discarded and not placed on the command stack. A subsequent undo or redo of this modified command must have the same effect as the two original commands. """ def redo(self): """ This is called by the command stack to redo the command. Any returned value will replace the value that the command stack references from the original call to 'do()' or previous call to 'redo()'. """ def undo(self): """ This is called by the command stack to undo the command. """ apptools-4.1.0/apptools/undo/api.py0000644000175100001440000000160111674464005020274 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ from abstract_command import AbstractCommand from command_stack import CommandStack from i_command import ICommand from i_command_stack import ICommandStack from i_undo_manager import IUndoManager from undo_manager import UndoManager apptools-4.1.0/apptools/undo/command_stack.py0000644000175100001440000002313511674464005022334 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import Bool, HasTraits, implements, Instance, \ Int, List, Property, Unicode # Local imports. from abstract_command import AbstractCommand from i_command import ICommand from i_command_stack import ICommandStack from i_undo_manager import IUndoManager class _StackEntry(HasTraits): """ The _StackEntry class is a single entry on a command stack. """ #### '_StackEntry' interface ############################################## # Set if the entry corresponds to a clean point on the stack. clean = Bool(False) # The command instance. command = Instance(ICommand) # The sequence number of the entry. sequence_nr = Int class _MacroCommand(AbstractCommand): """ The _MacroCommand class is an internal command that handles macros. """ #### '_MacroCommand' interface ############################################ # The commands that make up this macro. macro_commands = List(Instance(ICommand)) ########################################################################### # 'ICommand' interface. ########################################################################### def do(self): """ Invoke the command. """ # This is a dummy. return None def merge(self, other): """ Try and merge a command. """ if len(self.macro_commands) == 0: merged = False else: merged = self.macro_commands[-1].merge(other) return merged def redo(self): """ Redo the sub-commands. """ for cmd in self.macro_commands: cmd.redo() # Macros cannot return values. return None def undo(self): """ Undo the sub-commands. """ for cmd in self.macro_commands: cmd.undo() class CommandStack(HasTraits): """ The CommandStack class is the default implementation of the ICommandStack interface. """ implements(ICommandStack) #### 'ICommandStack' interface ############################################ # This is the clean state of the stack. Its value changes as commands are # undone and redone. It can also be explicity set to mark the current # stack position as being clean (when the data is saved to disk for # example). clean = Property(Bool) # This is the name of the command that can be redone. It will be empty if # there is no command that can be redone. It is maintained by the undo # stack. redo_name = Property(Unicode) # This is the undo manager that manages this stack. undo_manager = Instance(IUndoManager) # This is the name of the command that can be undone. It will be empty if # there is no command that can be undone. It is maintained by the undo # stack. undo_name = Property(Unicode) #### Private interface #################################################### # The current index into the stack (ie. the last command that was done). _index = Int(-1) # The current macro stack. _macro_stack = List(Instance(_MacroCommand)) # The stack itself. _stack = List(Instance(_StackEntry)) ########################################################################### # 'ICommandStack' interface. ########################################################################### def begin_macro(self, name): """ This begins a macro by creating an empty command with the given 'name'. All subsequent calls to 'push()' create commands that will be children of the empty command until the next call to 'end_macro()'. Macros may be nested. The stack is disabled (ie. nothing can be undone or redone) while a macro is being created (ie. while there is an outstanding 'end_macro()' call). """ command = _MacroCommand(name=name) self.push(command) self._macro_stack.append(command) def clear(self): """ This clears the stack, without undoing or redoing any commands, and leaves the stack in a clean state. It is typically used when all changes to the data have been abandoned. """ self._index = -1 self._stack = [] self._macro_stack = [] self.undo_manager.stack_updated = self def end_macro(self): """ This ends a macro. """ try: self._macro_stack.pop() except IndexError: pass def push(self, command): """ This executes a command and saves it on the command stack so that it can be subsequently undone and redone. 'command' is an instance that implements the ICommand interface. Its 'do()' method is called to execute the command. If any value is returned by 'do()' then it is returned by 'push()'. """ # See if the command can be merged with the previous one. if len(self._macro_stack) == 0: if self._index >= 0: merged = self._stack[self._index].command.merge(command) else: merged = False else: merged = self._macro_stack[-1].merge(command) # Increment the global sequence number. if not merged: self.undo_manager.sequence_nr += 1 # Execute the command. result = command.do() # Do nothing more if the command was merged. if merged: return result # Only update the command stack if there is no current macro. if len(self._macro_stack) == 0: # Remove everything on the stack after the last command that was # done. self._index += 1 del self._stack[self._index:] # Create a new stack entry and add it to the stack. entry = _StackEntry(command=command, sequence_nr=self.undo_manager.sequence_nr) self._stack.append(entry) self.undo_manager.stack_updated = self else: # Add the command to the parent macro command. self._macro_stack[-1].macro_commands.append(command) return result def redo(self, sequence_nr=0): """ If 'sequence_nr' is 0 then the last command that was undone is redone and any result returned. Otherwise commands are redone up to and including the given 'sequence_nr' and any result of the last of these is returned. """ # Make sure a redo is valid in the current context. if self.redo_name == "": return None if sequence_nr == 0: result = self._redo_one() else: result = None while self._index + 1 < len(self._stack): if self._stack[self._index + 1].sequence_nr > sequence_nr: break result = self._redo_one() self.undo_manager.stack_updated = self return result def undo(self, sequence_nr=0): """ If 'sequence_nr' is 0 then the last command is undone. Otherwise commands are undone up to and including the given 'sequence_nr'. """ # Make sure an undo is valid in the current context. if self.undo_name == "": return if sequence_nr == 0: self._undo_one() else: while self._index >= 0: if self._stack[self._index].sequence_nr <= sequence_nr: break self._undo_one() self.undo_manager.stack_updated = self ########################################################################### # Private interface. ########################################################################### def _redo_one(self): """ Redo the command at the current index and return the result. """ self._index += 1 entry = self._stack[self._index] return entry.command.redo() def _undo_one(self): """ Undo the command at the current index. """ entry = self._stack[self._index] self._index -= 1 entry.command.undo() def _get_clean(self): """ Get the clean state of the stack. """ if self._index >= 0: clean = self._stack[self._index].clean else: clean = True return clean def _set_clean(self, clean): """ Set the clean state of the stack. """ if self._index >= 0: self._stack[self._index].clean = clean def _get_redo_name(self): """ Get the name of the redo command, if any. """ redo_name = "" if len(self._macro_stack) == 0 and self._index + 1 < len(self._stack): redo_name = self._stack[self._index + 1].command.name.replace('&', '') return redo_name def _get_undo_name(self): """ Get the name of the undo command, if any. """ undo_name = "" if len(self._macro_stack) == 0 and self._index >= 0: command = self._stack[self._index].command undo_name = command.name.replace('&', '') return undo_name apptools-4.1.0/apptools/undo/i_command_stack.py0000644000175100001440000000750211674464005022644 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import Bool, Instance, Interface, Unicode # Local imports. from i_undo_manager import IUndoManager class ICommandStack(Interface): """ The command stack interface. A command stack is responsible for managing the changes to a data model and recording those changes so that they can be undone or redone. """ #### 'ICommandStack' interface ############################################ # This is the clean state of the stack. Its value changes as commands are # undone and redone. It can also be explicity set to mark the current # stack position as being clean (when the data is saved to disk for # example). clean = Bool # This is the name of the command that can be redone. It will be empty if # there is no command that can be redone. It is maintained by the undo # stack. redo_name = Unicode # This is the undo manager that manages this stack. undo_manager = Instance(IUndoManager) # This is the name of the command that can be undone. It will be empty if # there is no command that can be undone. It is maintained by the undo # stack. undo_name = Unicode ########################################################################### # 'ICommandStack' interface. ########################################################################### def begin_macro(self, name): """ This begins a macro by creating an empty command with the given 'name'. The commands passed to all subsequent calls to 'push()' will be contained in the macro until the next call to 'end_macro()'. Macros may be nested. The stack is disabled (ie. nothing can be undone or redone) while a macro is being created (ie. while there is an outstanding 'end_macro()' call). """ def clear(self): """ This clears the stack, without undoing or redoing any commands, and leaves the stack in a clean state. It is typically used when all changes to the data have been abandoned. """ def end_macro(self): """ This ends a macro. """ def push(self, command): """ This executes a command and saves it on the command stack so that it can be subsequently undone and redone. 'command' is an instance that implements the ICommand interface. Its 'do()' method is called to execute the command. If any value is returned by 'do()' then it is returned by 'push()'. The command stack will keep a reference to the result so that it can recognise it as an argument to a subsequent command (which allows a script to properly save a result needed later). """ def redo(self, sequence_nr=0): """ If 'sequence_nr' is 0 then the last command that was undone is redone and any result returned. Otherwise commands are redone up to and including the given 'sequence_nr' and any result of the last of these is returned. """ def undo(self, sequence_nr=0): """ If 'sequence_nr' is 0 then the last command is undone. Otherwise commands are undone up to and including the given 'sequence_nr'. """ apptools-4.1.0/apptools/undo/i_undo_manager.py0000644000175100001440000000510011674464005022470 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import Bool, Event, Instance, Int, Interface, Unicode class IUndoManager(Interface): """ The undo manager interface. An undo manager is responsible for one or more command stacks. Typically an application would have a single undo manager. """ #### 'IUndoManager' interface ############################################# # This is the currently active command stack and may be None. Typically it # is set when some sort of editor becomes active. active_stack = Instance('apptools.undo.api.ICommandStack') # This reflects the clean state of the currently active command stack. It # is intended to support a "document modified" indicator in the GUI. It is # maintained by the undo manager. active_stack_clean = Bool # This is the name of the command that can be redone. It will be empty if # there is no command that can be redone. It is maintained by the undo # manager. redo_name = Unicode # This is the sequence number of the next command to be performed. It is # incremented immediately before a command is invoked (by its 'do()' # method). sequence_nr = Int # This event is fired when the index of a command stack changes. Note that # it may not be the active stack. stack_updated = Event(Instance('apptools.undo.api.ICommandStack')) # This is the name of the command that can be undone. It will be empty if # there is no command that can be undone. It is maintained by the undo # manager. undo_name = Unicode ########################################################################### # 'IUndoManager' interface. ########################################################################### def redo(self): """ Redo the last undone command of the active command stack. """ def undo(self): """ Undo the last command of the active command stack. """ apptools-4.1.0/apptools/undo/abstract_command.py0000644000175100001440000000565211674464005023036 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import Any, HasTraits, implements, Unicode # Local imports. from i_command import ICommand class AbstractCommand(HasTraits): """ The AbstractCommand class is an abstract base class that implements the ICommand interface. """ implements(ICommand) #### 'ICommand' interface ################################################# # This is the data on which the command operates. data = Any # This is the name of the command as it will appear in any GUI element. It # may include '&' which will be automatically removed whenever it is # inappropriate. name = Unicode ########################################################################### # 'ICommand' interface. ########################################################################### def do(self): """ This is called by the command stack to do the command and to return any value. The command must save any state necessary for the 'redo()' and 'undo()' methods to work. The class's __init__() must also ensure that deep copies of any arguments are made if appropriate. It is guaranteed that this will only ever be called once and that it will be called before any call to 'redo()' or 'undo()'. """ raise NotImplementedError def merge(self, other): """ This is called by the command stack to try and merge another command with this one. True is returned if the commands were merged. 'other' is the command that is about to be executed. If the commands are merged then 'other' will discarded and not placed on the command stack. A subsequent undo or redo of this modified command must have the same effect as the two original commands. """ # By default merges never happen. return False def redo(self): """ This is called by the command stack to redo the command. Any returned value will replace the value that the command stack references from the original call to 'do()' or previous call to 'redo()'. """ raise NotImplementedError def undo(self): """ This is called by the command stack to undo the command. """ raise NotImplementedError apptools-4.1.0/apptools/undo/action/0000755000175100001440000000000011674464005020430 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/undo/action/command_action.py0000644000175100001440000000374611674464005023767 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from pyface.action.api import Action from traits.api import Any, Callable, Instance from apptools.undo.api import ICommand, ICommandStack class CommandAction(Action): """ The CommandAction class is an Action class that wrap undo/redo commands. It is only useful for commands that do not take any arguments or return any result. """ #### 'CommandAction' interface ############################################ # The command to create when the action is performed. command = Callable # The command stack onto which the command will be pushed when the action # is performed. command_stack = Instance(ICommandStack) # This is the data on which the command operates. data = Any ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """ This is reimplemented to push a new command instance onto the command stack. """ self.command_stack.push(self.command(data=self.data)) def _name_default(self): """ This gets the action name from the command. """ if self.command: name = self.command().name else: name = "" return name apptools-4.1.0/apptools/undo/action/api.py0000644000175100001440000000140611674464005021554 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ from command_action import CommandAction from redo_action import RedoAction from undo_action import UndoAction apptools-4.1.0/apptools/undo/action/redo_action.py0000644000175100001440000000322711674464005023274 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Local imports. from abstract_command_stack_action import AbstractCommandStackAction class RedoAction(AbstractCommandStackAction): """ An action that redos the last command undone of the active command stack. """ ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """ Perform the action. """ self.undo_manager.redo() ########################################################################### # 'AbstractUndoAction' interface. ########################################################################### def _update_action(self): """ Update the state of the action. """ name = self.undo_manager.redo_name if name: name = "&Redo " + name self.enabled = True else: name = "&Redo" self.enabled = False self.name = name apptools-4.1.0/apptools/undo/action/abstract_command_stack_action.py0000644000175100001440000000440511674464005027030 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from pyface.action.api import Action from traits.api import Instance from apptools.undo.api import IUndoManager class AbstractCommandStackAction(Action): """ The abstract base class for all actions that operate on a command stack. """ #### 'AbstractCommandStackAction' interface ############################### # The undo manager. undo_manager = Instance(IUndoManager) ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **traits): """ Initialise the instance. """ super(AbstractCommandStackAction, self).__init__(**traits) self.undo_manager.on_trait_event(self._on_stack_updated, 'stack_updated') # Update the action to initialise it. self._update_action() ########################################################################### # Protected interface. ########################################################################### def _update_action(self): """ Update the state of the action. """ raise NotImplementedError ########################################################################### # Private interface. ########################################################################### def _on_stack_updated(self, stack): """ Handle changes to the state of a command stack. """ # Ignore unless it is the active stack. if stack is self.undo_manager.active_stack: self._update_action() apptools-4.1.0/apptools/undo/action/__init__.py0000644000175100001440000000064711674464005022550 0ustar ischnellusers00000000000000# Copyright (c) 2005-2011, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. apptools-4.1.0/apptools/undo/action/undo_action.py0000644000175100001440000000321011674464005023300 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Local imports. from abstract_command_stack_action import AbstractCommandStackAction class UndoAction(AbstractCommandStackAction): """ An action that undos the last command of the active command stack. """ ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """ Perform the action. """ self.undo_manager.undo() ########################################################################### # 'AbstractUndoAction' interface. ########################################################################### def _update_action(self): """ Update the state of the action. """ name = self.undo_manager.undo_name if name: name = "&Undo " + name self.enabled = True else: name = "&Undo" self.enabled = False self.name = name apptools-4.1.0/apptools/undo/__init__.py0000644000175100001440000000104211674464005021261 0ustar ischnellusers00000000000000# Copyright (c) 2005-2011, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. """ Supports undoing and scripting application commands. Part of the AppTools project of the Enthought Tool Suite. """ apptools-4.1.0/apptools/scripting/0000755000175100001440000000000011674464005020210 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/scripting/util.py0000644000175100001440000000266111674464005021544 0ustar ischnellusers00000000000000"""Simple utility functions provided by the scripting API. """ # Author: Prabhu Ramachandran # Copyright (c) 2008, Prabhu Ramachandran # License: BSD Style. from recorder import Recorder from recorder_with_ui import RecorderWithUI from package_globals import get_recorder, set_recorder ################################################################################ # Utility functions. ################################################################################ def start_recording(object, ui=True, **kw): """Convenience function to start recording. Returns the recorder. Parameters: ----------- object : object to record. ui : bool specifying if a UI is to be shown or not kw : Keyword arguments to pass to the register function of the recorder. """ if ui: r = RecorderWithUI(root=object) r.edit_traits(kind='live') else: r = Recorder() # Set the global recorder. set_recorder(r) r.recording = True r.register(object, **kw) return r def stop_recording(object, save=True): """Stop recording the object. If `save` is `True`, this will pop up a UI to ask where to save the script. """ recorder = get_recorder() recorder.unregister(object) recorder.recording = False # Set the global recorder back to None set_recorder(None) # Save the script. if save: recorder.ui_save() apptools-4.1.0/apptools/scripting/tests/0000755000175100001440000000000011674464005021352 5ustar ischnellusers00000000000000apptools-4.1.0/apptools/scripting/tests/test_recorder.py0000644000175100001440000003467311674464005024605 0ustar ischnellusers00000000000000""" Unit tests for the script recorder. """ # Author: Prabhu Ramachandran # Copyright (c) 2008, Enthought, Inc. # License: BSD Style. import unittest from traits.api import (HasTraits, Float, Instance, Str, List, Bool, HasStrictTraits, Tuple, Range, TraitPrefixMap, Trait) from apptools.scripting.api import (Recorder, recordable, set_recorder) ###################################################################### # Test classes. class Property(HasStrictTraits): color = Tuple(Range(0.0, 1.0), Range(0.0, 1.0), Range(0.0, 1.0)) opacity = Range(0.0, 1.0, 1.0) representation = Trait('surface', TraitPrefixMap({'surface':2, 'wireframe': 1, 'points': 0})) class Toy(HasTraits): color = Str type = Str ignore = Bool(False, record=False) class Child(HasTraits): name = Str('child') age = Float(10.0) property = Instance(Property, (), record=True) toy = Instance(Toy, record=True) friends = List(Str) @recordable def grow(self, x): """Increase age by x years.""" self.age += x self.f(1) @recordable def f(self, args): """Function f.""" return args def not_recordable(self): pass class Parent(HasTraits): children = List(Child, record=True) recorder = Instance(Recorder, record=False) class Test(HasTraits): # This should be set. recorder = Instance(HasTraits) # These should be ignored. _ignore = Bool(False) ignore_ = Bool(False) class TestRecorder(unittest.TestCase): def setUp(self): self.tape = Recorder() set_recorder(self.tape) p = Parent() c = Child() toy = Toy(color='blue', type='bunny') c.toy = toy p.children.append(c) self.p = p return def tearDown(self): self.tape.clear() set_recorder(None) return def test_unique_name(self): "Does the get_unique_id method work." class XMLUnstructuredGridWriter: pass t = XMLUnstructuredGridWriter() tape = self.tape self.assertEqual(tape._get_unique_name(t), 'xml_unstructured_grid_writer') self.assertEqual(tape._get_unique_name(t), 'xml_unstructured_grid_writer1') t = Toy() self.assertEqual(tape._get_unique_name(t), 'toy') t = (1, 2) self.assertEqual(tape._get_unique_name(t), 'tuple0') l = [1, 2] self.assertEqual(tape._get_unique_name(l), 'list0') d = {'a': 1} self.assertEqual(tape._get_unique_name(d), 'dict0') self.assertEqual(tape._get_unique_name(1), 'int0') def test_record(self): "Does recording work correctly." tape = self.tape p = self.p c = p.children[0] toy = c.toy # start recording. tape.recording = True tape.register(p) # Test if p's recorder attribute is set. self.assertEqual(tape, p.recorder) # Test script ids and object path. self.assertEqual(tape.get_script_id(p), 'parent') self.assertEqual(tape.get_object_path(p), '') self.assertEqual(tape.get_script_id(c), 'child') self.assertEqual(tape.get_object_path(c), 'parent.children[0]') self.assertEqual(tape.get_script_id(toy), 'child.toy') self.assertEqual(tape.get_object_path(toy), 'parent.children[0].toy') c.name = 'Ram' # The child should first be instantiated. self.assertEqual(tape.lines[-2], "child = parent.children[0]") # Then its trait set. self.assertEqual(tape.lines[-1], "child.name = 'Ram'") c.age = 10.5 self.assertEqual(tape.lines[-1], "child.age = 10.5") c.property.representation = 'w' self.assertEqual(tape.lines[-1], "child.property.representation = 'wireframe'") c.property.color = (1, 0, 0) self.assertEqual(tape.lines[-1], "child.property.color = (1.0, 0.0, 0.0)") toy.color = 'red' self.assertEqual(tape.lines[-1], "child.toy.color = 'red'") toy.type = 'teddy' self.assertEqual(tape.lines[-1], "child.toy.type = 'teddy'") # This trait should be ignored. toy.ignore = True self.assertEqual(tape.lines[-1], "child.toy.type = 'teddy'") # Turn of recording and test. tape.recording = False toy.type = 'rat' self.assertEqual(tape.lines[-1], "child.toy.type = 'teddy'") #print tape.script # Stop recording. n = len(tape.lines) tape.unregister(p) c.property.representation = 'points' toy.type = 'bunny' self.assertEqual(tape.lines[-1], "child.toy.type = 'teddy'") self.assertEqual(n, len(tape.lines)) # Make sure the internal data of the recorder is cleared. self.assertEqual(0, len(tape._registry)) self.assertEqual(0, len(tape._reverse_registry)) self.assertEqual(0, len(tape._known_ids)) def test_recorded_trait_replaced(self): "Does recording work right when a trait is replaced." tape = self.tape p = self.p c = p.children[0] toy = c.toy # start recording. tape.recording = True tape.register(p) # Test the original trait. toy.color = 'red' self.assertEqual(tape.lines[-1], "child.toy.color = 'red'") # Now reassign the toy. t1 = Toy(name='ball') c.toy = t1 t1.color = 'yellow' self.assertEqual(tape.lines[-1], "child.toy.color = 'yellow'") def test_clear(self): "Test the clear method." p = self.p tape = self.tape tape.register(p) tape.clear() # Everything should be unregistered. self.assertEqual(p.recorder, None) # Internal data should be wiped clean. self.assertEqual(0, len(tape._registry)) self.assertEqual(0, len(tape._reverse_registry)) self.assertEqual(0, len(tape._known_ids)) self.assertEqual(0, len(tape._name_map)) def test_create_object(self): "Is the object imported and created if unknown?" tape = self.tape tape.recording = True t = Toy() tape.register(t) t.type = 'computer' # Since the name toy is unknown, there should be a # line to create it. self.assertEqual(tape.lines[-3][-10:], "import Toy") self.assertEqual(tape.lines[-2], "toy = Toy()") self.assertEqual(tape.lines[-1], "toy.type = 'computer'") # Since this one is known, there should be no imports or # anything. t1 = Toy() tape.register(t1, known=True) t1.type = 'ball' self.assertEqual(tape.lines[-2], "toy.type = 'computer'") self.assertEqual(tape.lines[-1], "toy1.type = 'ball'") def test_list_items_changed(self): "Test if a list item is changed does the change get recorded." p = self.p tape = self.tape child = p.children[0] tape.register(p, known=True) tape.recording = True child.friends = ['Krishna', 'Ajay', 'Ali'] self.assertEqual(tape.lines[-1], "child.friends = ['Krishna', 'Ajay', 'Ali']") child.friends[1:] = ['Sam', 'Frodo'] self.assertEqual(tape.lines[-1], "child.friends[1:3] = ['Sam', 'Frodo']") child.friends[1] = 'Hari' self.assertEqual(tape.lines[-1], "child.friends[1:2] = ['Hari']") # What if we change a list where record=True. child1 = Child() tape.register(child1) p.children.append(child1) self.assertEqual(tape.lines[-1], "parent.children[1:1] = [child1]") del p.children[1] self.assertEqual(tape.lines[-1], "parent.children[1:2] = []") p.children[0] = child1 self.assertEqual(tape.lines[-1], "parent.children[0:1] = [child1]") def test_path_change_on_list(self): "Does the object path update when a list has changed?" # Test the case where we have a hierarchy and we change the # list. tape = self.tape p = self.p child1 = Child() p.children.append(child1) tape.register(p) tape.recording = True self.assertEqual(tape.get_object_path(child1), 'parent.children[1]') self.assertEqual(tape.get_script_id(child1), 'child1') del p.children[0] self.assertEqual(tape.get_object_path(child1), 'parent.children[0]') self.assertEqual(tape.get_script_id(child1), 'child1') def test_write_script_id_in_namespace(self): "Test the write_script_id_in_namespace method." tape = self.tape tape.recording = True # This should not cause an error but insert the name 'foo' in the # namespace. tape.write_script_id_in_namespace('foo') def test_recorder_and_ignored(self): "Test if recorder trait is set and private traits are ignored." t = Test() self.assertEqual(t.recorder, None) self.assertEqual(t._ignore, False) self.assertEqual(t.ignore_, False) tape = Recorder() tape.register(t) tape.recording = True self.assertEqual(t.recorder, tape) t._ignore = True t.ignore_ = True self.assertEqual(len(tape.script.strip()), 0) def test_record_function(self): "See if recordable function calls are handled correctly." # Note that the global recorder is set in setUp and removed in # tearDown. tape = self.tape c = self.p.children[0] tape.register(c) tape.recording = True # Setting the age should be recorded. c.age = 11 self.assertEqual(tape.lines[-1], "child.age = 11.0") # This should also work without problems. c.f(c.toy) self.assertEqual(tape.lines[-2], "child.age = 11.0") self.assertEqual(tape.lines[-1], 'child.toy = child.f(child.toy)') # Calling f should be recorded. c.f(1) self.assertEqual(tape.lines[-1], "child.f(1)") # This should not record the call to f or the change to the age # trait inside grow. c.grow(1) self.assertEqual(c.age, 12.0) self.assertEqual(tape.lines[-2], "child.f(1)") self.assertEqual(tape.lines[-1], "child.grow(1)") # Non-recordable functions shouldn't be. c.not_recordable() self.assertEqual(tape.lines[-1], "child.grow(1)") # Test a simple recordable function. @recordable def func(x, y): return x, y result = func(1, 2) self.assertEqual(tape.lines[-1], "tuple0 = func(1, 2)") def test_non_has_traits(self): "Can classes not using traits be handled?" tape = self.tape p = self.p c = p.children[0] class A(object): @recordable def __init__(self, x, y=1): self.x = x self.y = y @recordable def f(self, x, y): return x, y @recordable def g(self, x): return x def not_recordable(self): pass tape.register(p) tape.recording = True # Test if __init__ is recorded correctly. a = A(x=1) # Should record. a.f(1, 'asd') self.assertEqual(tape.lines[-3][-8:], "import A") self.assertEqual(tape.lines[-2], "a = A(x=1)") self.assertEqual(tape.lines[-1], "tuple0 = a.f(1, 'asd')") result = a.f(p, c) # This should instantiate the parent first, get the child from # that and then record the call itself. self.assertEqual(tape.lines[-3], "parent = Parent()") self.assertEqual(tape.lines[-2], "child = parent.children[0]") self.assertEqual(tape.lines[-1], "tuple1 = a.f(parent, child)") # This should simply refer to the child. result = a.g(c) self.assertEqual(tape.lines[-1], "child = a.g(child)") # Should do nothing. a.not_recordable() self.assertEqual(tape.lines[-1], "child = a.g(child)") # When a function is called with unknown args it should attempt # to create the objects. r = a.g(Toy()) self.assertEqual(tape.lines[-3][-10:], "import Toy") self.assertEqual(tape.lines[-2], "toy = Toy()") self.assertEqual(tape.lines[-1], "toy = a.g(toy)") def test_set_script_id(self): "Test if setting script_id at registration time works." tape = self.tape p = self.p c = p.children[0] tape.register(p, script_id='child') tape.recording = True # Ask to be called child. self.assertEqual(tape.get_script_id(p), 'child') # Register another Child. c1 = Child() tape.register(c1) # Will be child2 since child1 is taken. self.assertEqual(tape.get_script_id(c1), 'child2') # Test if recording works correctly with the changed script_id. p.children.append(c1) self.assertEqual(tape.lines[-1], "child.children[1:1] = [child2]") def test_save(self): "Test if saving tape to file works." tape = self.tape p = self.p c = p.children[0] toy = c.toy # Start recording tape.register(p) tape.recording = True toy.type = 'teddy' # Now stop. tape.recording = False tape.unregister(p) import StringIO f = StringIO.StringIO() tape.save(f) # Test if the file is OK. expect = ["child = parent.children[0]\n", "child.toy.type = 'teddy'\n" ] f.seek(0) lines = f.readlines() self.assertEqual(expect, lines) f.close() if __name__ == '__main__': unittest.main() apptools-4.1.0/apptools/scripting/api.py0000644000175100001440000000063511674464005021337 0ustar ischnellusers00000000000000"""Public API for the scripting package. """ # Author: Prabhu Ramachandran # Copyright (c) 2008, Prabhu Ramachandran and Enthought, Inc. # License: BSD Style. from recorder import Recorder, RecorderError from recordable import recordable from package_globals import get_recorder, set_recorder from recorder_with_ui import RecorderWithUI from util import start_recording, stop_recording apptools-4.1.0/apptools/scripting/recorder_with_ui.py0000644000175100001440000000567711674464005024136 0ustar ischnellusers00000000000000""" A Recorder subclass that presents a simple user interface. """ # Author: Prabhu Ramachandran # Copyright (c) 2008, Prabhu Ramachandran. # License: BSD Style. from traits.api import Code, Button, Int, on_trait_change, Any from traitsui.api import (View, Item, Group, HGroup, CodeEditor, spring, Handler) from recorder import Recorder ###################################################################### # `CloseHandler` class. ###################################################################### class CloseHandler(Handler): """This class cleans up after the UI for the recorder is closed.""" def close(self, info, is_ok): """This method is invoked when the user closes the UI.""" recorder = info.object recorder.on_ui_close() return True ################################################################################ # `RecorderWithUI` class. ################################################################################ class RecorderWithUI(Recorder): """ This class represents a Recorder but with a simple user interface. """ # The code to display code = Code(editor=CodeEditor(line='current_line')) # Button to save script to file. save_script = Button('Save Script') # The current line to show, used by the editor. current_line = Int # The root object which is being recorded. root = Any ######################################## # Traits View. view = View( Group( HGroup(Item('recording', show_label=True), spring, Item('save_script', show_label=False), ), Group(Item('code', show_label=False)), ), width=600, height=360, id='apptools.scripting.recorder_with_ui', buttons=['Cancel'], resizable=True, handler=CloseHandler() ) ###################################################################### # RecorderWithUI interface. ###################################################################### def on_ui_close(self): """Called from the CloseHandler when the UI is closed. This method basically stops the recording. """ from util import stop_recording from package_globals import get_recorder if get_recorder() is self: stop_recording(self.root, save=False) else: self.recording = False self.unregister(self.root) ###################################################################### # Non-public interface. ###################################################################### @on_trait_change('lines[]') def _update_code(self): self.code = self.get_code() self.current_line = len(self.lines) + 1 def _save_script_fired(self): self.ui_save() apptools-4.1.0/apptools/scripting/recorder.py0000644000175100001440000006371011674464005022376 0ustar ischnellusers00000000000000""" Code to support recording to a readable and executable Python script. TODO: - Support for dictionaries? """ # Author: Prabhu Ramachandran # Copyright (c) 2008, Enthought, Inc. # License: BSD Style. import warnings import types import __builtin__ from traits.api import (HasTraits, List, Str, Dict, Bool, Unicode, Property, Int, Instance) from traits.util.camel_case import camel_case_to_python ################################################################################ # `_RegistryData` class. ################################################################################ class _RegistryData(HasTraits): # Object's script ID script_id = Property(Str) # Path to object in object hierarchy. path = Property(Str) # Parent data for this object if any. parent_data = Instance('_RegistryData', allow_none=True) # The name of the trait on the parent which is this object. trait_name_on_parent = Str('') # List of traits we are listening for on this object. names = List(Str) # Nested recordable instances on the object. sub_recordables = List(Str) # List of traits that are lists. list_names = List(Str) _script_id = Str('') ###################################################################### # Non-public interface. ###################################################################### def _get_path(self): pdata = self.parent_data path = '' if pdata is not None: pid = pdata.script_id ppath = pdata.path tnop = self.trait_name_on_parent if '[' in tnop: # If the object is a nested object through an iterator, # we instantiate it and don't refer to it through the # path, this makes scripting convenient. if len(ppath) == 0: path = pid + '.' + tnop else: path = ppath + '.' + tnop else: path = ppath + '.' + tnop return path def _get_script_id(self): sid = self._script_id if len(sid) == 0: pdata = self.parent_data sid = pdata.script_id + '.' + self.trait_name_on_parent return sid def _set_script_id(self, id): self._script_id = id ################################################################################ # `RecorderError` class. ################################################################################ class RecorderError(Exception): pass ################################################################################ # `Recorder` class. ################################################################################ class Recorder(HasTraits): # The lines of code recorded. lines = List(Str) # Are we recording or not? recording = Bool(False, desc='if script recording is enabled or not') # The Python script we have recorded so far. This is just a # convenience trait for the `get_code()` method. script = Property(Unicode) ######################################## # Private traits. # Dict used to store information on objects registered. It stores a # unique name for the object and its path in the object hierarchy # traversed. _registry = Dict # Reverse registry with keys as script_id and object as value. _reverse_registry = Dict # A mapping to generate unique names for objects. The key is the # name used (which is something derived from the class name of the # object) and the value is an integer describing the number of times # that variable name has been used earlier. _name_map = Dict(Str, Int) # A list of special reserved script IDs. This is handy when you # want a particular object to have an easy to read script ID and not # the default one based on its class name. This leads to slightly # easier to read scripts. _special_ids = List # What are the known names in the script? By known names we mean # names which are actually bound to objects. _known_ids = List(Str) # The known types in the namespace. _known_types = List(Str) # A guard to check if we are currently in a recorded function call, # in which case we don't want to do any recording. _in_function = Bool(False) ###################################################################### # `Recorder` interface. ###################################################################### def record(self, code): """Record a string to be stored to the output file. Parameters: ----------- code - A string of text. """ if self.recording and not self._in_function: lines = self.lines # Analyze the code and add extra code if needed. self._analyze_code(code) # Add the code. lines.append(code) def register(self, object, parent=None, trait_name_on_parent='', ignore=None, known=False, script_id=None): """Register an object with the recorder. This sets up the object for recording. By default all traits (except those starting and ending with '_') are recorded. For attributes that are themselves recordable, one may mark traits with a 'record' metadata as follows: - If metadata `record=False` is set, the nested object will not be recorded. - If `record=True`, then that object is also recorded if it is not `None`. If the object is a list or dict that is marked with `record=True`, the list is itself not listened to for changes but all its contents are registered. If the `object` has a trait named `recorder` then this recorder instance will be set to it if possible. Parameters: ----------- object : Instance(HasTraits) The object to register in the registry. parent : Instance(HasTraits) An optional parent object in which `object` is contained trait_name_on_parent : str An optional trait name of the `object` in the `parent`. ignore : list(str) An optional list of trait names on the `object` to be ignored. known : bool Optional specification if the `object` id is known on the interpreter. This is needed if you are manually injecting code to define/create an object. script_id : str Optionally specify a script_id to use for this object. It is not guaranteed that this ID will be used since it may already be in use. """ registry = self._registry # Do nothing if the object is already registered. if object in registry: return # When parent is specified the trait_name_on_parent must also be. if parent is not None: assert len(trait_name_on_parent) > 0 if ignore is None: ignore = [] if isinstance(object, HasTraits): # Always ignore these. ignore.extend(['trait_added', 'trait_modified']) sub_recordables = object.traits(record=True).keys() # Find all the trait names we must ignore. ignore.extend(object.traits(record=False).keys()) # The traits to listen for. tnames = [t for t in object.trait_names() if not t.startswith('_') and not t.endswith('_') \ and t not in ignore] # Find all list traits. trts = object.traits() list_names = [] for t in tnames: tt = trts[t].trait_type if hasattr(tt, 'default_value_type') and \ tt.default_value_type == 5: list_names.append(t) else: # No traits, so we can't do much. sub_recordables = [] tnames = [] list_names = [] # Setup the registry data. # If a script id is supplied try and use it. sid = '' if script_id is not None: r_registry = self._reverse_registry while script_id in r_registry: script_id = '%s1'%script_id sid = script_id # Add the chosen id to special_id list. self._special_ids.append(sid) if parent is None: pdata = None if len(sid) == 0: sid = self._get_unique_name(object) else: pdata = self._get_registry_data(parent) tnop = trait_name_on_parent if '[' in tnop: # If the object is a nested object through an iterator, # we instantiate it and don't refer to it through the # path, this makes scripting convenient. sid = self._get_unique_name(object) # Register the object with the data. data = _RegistryData(script_id=sid, parent_data=pdata, trait_name_on_parent=trait_name_on_parent, names=tnames, sub_recordables=sub_recordables, list_names=list_names) registry[object] = data # Now get the script id of the object -- note that if sid is '' # above then the script_id is computed from that of the parent. sid = data.script_id # Setup reverse registry so we can get the object from the # script_id. self._reverse_registry[sid] = object # Record the script_id if the known argument is explicitly set to # True. if known: self._known_ids.append(sid) # Try and set the recorder attribute if necessary. if hasattr(object, 'recorder'): try: object.recorder = self except Exception, e: msg = "Cannot set 'recorder' trait of object %r: "\ "%s"%(object, e) warnings.warn(msg, warnings.RuntimeWarning) if isinstance(object, HasTraits): # Add handler for lists. for name in list_names: object.on_trait_change(self._list_items_listner, '%s_items'%name) # Register all sub-recordables. for name in sub_recordables: obj = getattr(object, name) if isinstance(obj, list): # Don't register the object itself but register its # children. for i, child in enumerate(obj): attr = '%s[%d]'%(name, i) self.register(child, parent=object, trait_name_on_parent=attr) elif obj is not None: self.register(obj, parent=object, trait_name_on_parent=name) # Listen for changes to the trait itself so the newly # assigned object can also be listened to. object.on_trait_change(self._object_changed_handler, name) # Now add listner for the object itself. object.on_trait_change(self._listner, tnames) def unregister(self, object): """Unregister the given object from the recorder. This inverts the logic of the `register(...)` method. """ registry = self._registry # Do nothing if the object isn't registered. if object not in registry: return data = registry[object] # Try and unset the recorder attribute if necessary. if hasattr(object, 'recorder'): try: object.recorder = None except Exception, e: msg = "Cannot unset 'recorder' trait of object %r:"\ "%s"%(object, e) warnings.warn(msg, warnings.RuntimeWarning) if isinstance(object, HasTraits): # Remove all list_items handlers. for name in data.list_names: object.on_trait_change(self._list_items_listner, '%s_items'%name, remove=True) # Unregister all sub-recordables. for name in data.sub_recordables: obj = getattr(object, name) if isinstance(obj, list): # Unregister the children. for i, child in enumerate(obj): self.unregister(child) elif obj is not None: self.unregister(obj) # Remove the trait handler for trait assignments. object.on_trait_change(self._object_changed_handler, name, remove=True) # Now remove listner for the object itself. object.on_trait_change(self._listner, data.names, remove=True) # Remove the object data from the registry etc. if data.script_id in self._known_ids: self._known_ids.remove(data.script_id) del self._reverse_registry[data.script_id] del registry[object] def save(self, file): """Save the recorded lines to the given file. It does not close the file. """ file.write(self.get_code()) file.flush() def record_function(self, func, args, kw): """Record a function call given the function and its arguments.""" if self.recording and not self._in_function: # Record the function name and arguments. call_str = self._function_as_string(func, args, kw) # Call the function. try: self._in_function = True result = func(*args, **kw) finally: self._in_function = False # Register the result if it is not None. if func.__name__ == '__init__': f_self = args[0] code = self._import_class_string(f_self.__class__) self.lines.append(code) return_str = self._registry.get(f_self).script_id else: return_str = self._return_as_string(result) if len(return_str) > 0: self.lines.append('%s = %s'%(return_str, call_str)) else: self.lines.append('%s'%(call_str)) else: result = func(*args, **kw) return result def ui_save(self): """Save recording to file, pop up a UI dialog to find out where and close the file when done. """ from pyface.api import FileDialog, OK wildcard = 'Python files (*.py)|*.py|' + FileDialog.WILDCARD_ALL dialog = FileDialog(title='Save Script', action='save as', wildcard=wildcard ) if dialog.open() == OK: fname = dialog.path f = open(fname, 'w') self.save(f) f.close() def clear(self): """Clears all previous recorded state and unregisters all registered objects.""" # First unregister any registered objects. registry = self._registry while len(registry) > 0: self.unregister(registry.keys()[0]) # Clear the various lists. self.lines[:] = [] self._registry.clear() self._known_ids[:] = [] self._name_map.clear() self._reverse_registry.clear() self._known_types[:] = [] self._special_ids[:] = [] def get_code(self): """Returns the recorded lines as a string of printable code.""" return '\n'.join(self.lines) + '\n' def is_registered(self, object): """Returns True if the given object is registered with the recorder.""" return object in self._registry def get_script_id(self, object): """Returns the script_id of a registered object. Useful when you want to manually add a record statement.""" return self._get_registry_data(object).script_id def get_object_path(self, object): """Returns the path in the object hierarchy of a registered object. Useful for debugging.""" return self._get_registry_data(object).path def write_script_id_in_namespace(self, script_id): """If a script_id is not known in the current script's namespace, this sets it using the path of the object or actually instantiating it. If this is not possible (since the script_id matches no existing object), nothing is recorded but the framework is notified that the particular script_id is available in the namespace. This is useful when you want to inject code in the namespace to create a particular object. """ if not self.recording: return known_ids = self._known_ids if script_id not in known_ids: obj = self._reverse_registry.get(script_id) # Add the ID to the known_ids. known_ids.append(script_id) if obj is not None: data = self._registry.get(obj) result = '' if len(data.path) > 0: # Record code for instantiation of object. result = '%s = %s'%(script_id, data.path) else: # This is not the best thing to do but better than # nothing. result = self._import_class_string(obj.__class__) cls = obj.__class__.__name__ mod = obj.__module__ result += '\n%s = %s()'%(script_id, cls) if len(result) > 0: self.lines.extend(result.split('\n')) ###################################################################### # Non-public interface. ###################################################################### def _get_unique_name(self, obj): """Return a unique object name (a string). Note that this does not cache the object, so if called with the same object 3 times you'll get three different names. """ cname = obj.__class__.__name__ nm = self._name_map result = '' builtin = False if cname in __builtin__.__dict__: builtin = True if hasattr(obj, '__name__'): cname = obj.__name__ else: cname = camel_case_to_python(cname) special_ids = self._special_ids while len(result) == 0 or result in special_ids: if cname in nm: id = nm[cname] + 1 nm[cname] = id result = '%s%d'%(cname, id) else: nm[cname] = 0 # The first id doesn't need a number if it isn't builtin. if builtin: result = '%s0'%(cname) else: result = cname return result def _get_registry_data(self, object): """Get the data for an object from registry.""" data = self._registry.get(object) if data is None: msg = "Recorder: Can't get script_id since object %s not registered" raise RecorderError(msg%(object)) return data def _listner(self, object, name, old, new): """The listner for trait changes on an object. This is called by child listners or when any of the recordable object's traits change when recording to a script is enabled. Parameters: ----------- object : Object which has changed. name : extended name of attribute that changed. old : Old value. new : New value. """ if self.recording and not self._in_function: new_repr = repr(new) sid = self._get_registry_data(object).script_id if len(sid) == 0: msg = '%s = %r'%(name, new) else: msg = '%s.%s = %r'%(sid, name, new) if new_repr.startswith('<') and new_repr.endswith('>'): self.record('# ' + msg) else: self.record(msg) def _list_items_listner(self, object, name, old, event): """The listner for *_items on list traits of the object. """ # Set the path of registered objects in the modified list and # all their children. This is done by unregistering the object # and re-registering them. This is slow but. registry = self._registry sid = registry.get(object).script_id trait_name = name[:-6] items = getattr(object, trait_name) for (i, item) in enumerate(items): if item in registry: data = registry.get(item) tnop = data.trait_name_on_parent if len(tnop) > 0: data.trait_name_on_parent = '%s[%d]'%(trait_name, i) # Record the change. if self.recording and not self._in_function: index = event.index removed = event.removed added = event.added nr = len(removed) slice = '[%d:%d]'%(index, index + nr) na = len(added) rhs = [self._object_as_string(item) for item in added] rhs = ', '.join(rhs) obj = '%s.%s'%(sid, name[:-6]) msg = '%s%s = [%s]'%(obj, slice, rhs) self.record(msg) def _object_changed_handler(self, object, name, old, new): """Called when a child recordable object has been reassigned.""" registry = self._registry if old is not None: if old in registry: self.unregister(old) if new is not None: if new not in registry: self.register(new, parent=object, trait_name_on_parent=name) def _get_script(self): return self.get_code() def _analyze_code(self, code): """Analyze the code and return extra code if needed. """ known_ids = self._known_ids lhs = '' try: lhs = code.split()[0] except IndexError: pass if '.' in lhs: ob_name = lhs.split('.')[0] self.write_script_id_in_namespace(ob_name) def _function_as_string(self, func, args, kw): """Return a string representing the function call.""" func_name = func.__name__ func_code = func.func_code # Even if func is really a decorated method it never shows up as # a bound or unbound method here, so we have to inspect the # argument names to figure out if this is a method or function. if func_code.co_argcount > 0 and \ func_code.co_varnames[0] == 'self': # This is a method, the first argument is bound to self. f_self = args[0] # Convert the remaining arguments to strings. argl = [self._object_as_string(arg) for arg in args[1:]] # If this is __init__ we special case it. if func_name == '__init__': # Register the object. self.register(f_self, known=True) func_name = f_self.__class__.__name__ else: sid = self._object_as_string(f_self) func_name = '%s.%s'%(sid, func_name) else: argl = [self._object_as_string(arg) for arg in args] # Convert the keyword args. kwl = ['%s=%s'%(key, self._object_as_string(value)) for key, value in kw.iteritems()] argl.extend(kwl) # Make a string representation of the args, kw. argstr = ', '.join(argl) return '%s(%s)'%(func_name, argstr) def _object_as_string(self, object): """Return a string representing the object. """ registry = self._registry if object in registry: # Return script id if the object is known; create the script # id on the namespace if needed before that. sid = registry.get(object).script_id base_id = sid.split('.')[0] self.write_script_id_in_namespace(base_id) return sid else: # Try and return the object. ob_id = id(object) orepr = repr(object) # As done in appscripting, we assume that if the hexid of # the object is in its string representation then it is an # arbitrary object. We remove the leading '0x' so that leading # zeros after the 'x' in the orepr string will not lead to a # mismatch. (ie. 0xA3E will not match 0x0A3E) if hex(ob_id)[2:].upper() not in orepr.upper(): return orepr # If we get here, we just register the object and call ourselves # again to do the needful. self.register(object) return self._object_as_string(object) def _return_as_string(self, object): """Return a string given a returned object from a function. """ result = '' ignore = (types.FloatType, types.ComplexType, types.BooleanType, types.IntType, types.LongType) + types.StringTypes if object is not None and type(object) not in ignore: # If object is not know, register it. registry = self._registry if object not in registry: self.register(object) result = registry.get(object).script_id # Since this is returned it is known on the namespace. known_ids = self._known_ids if result not in known_ids: known_ids.append(result) return result def _import_class_string(self, cls): """Import a class if needed. """ cname = cls.__name__ result = '' if cname not in __builtin__.__dict__: mod = cls.__module__ typename = '%s.%s'%(mod, cname) if typename not in self._known_types: result = 'from %s import %s'%(mod, cname) self._known_types.append(typename) return result apptools-4.1.0/apptools/scripting/__init__.py0000644000175100001440000000102711674464005022321 0ustar ischnellusers00000000000000# Copyright (c) 2008-2011, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Prabhu Ramachandran """ Automatic script recording framework, part of the AppTools project of the Enthought Tool Suite. """ apptools-4.1.0/apptools/scripting/recordable.py0000644000175100001440000000314611674464005022670 0ustar ischnellusers00000000000000""" Decorator to mark functions and methods as recordable. """ # Author: Prabhu Ramachandran # Copyright (c) 2008, Prabhu Ramachandran and Enthought, Inc. # License: BSD Style. from package_globals import get_recorder # Guard to ensure that only the outermost recordable call is recorded # and nested calls ignored. _outermost_call = True def recordable(func): """A decorator that wraps a function into one that is recordable. This will record the function only if the global recorder has been set via a `set_recorder` function call. This is almost entirely copied from the apptools.appscripting.scriptable.scriptable decorator. """ def _wrapper(*args, **kw): """A wrapper returned to replace the decorated function.""" global _outermost_call # Boolean to specify if the method was recorded or not. record = False if _outermost_call: # Get the recorder. rec = get_recorder() if rec is not None: _outermost_call = False # Record the method if recorder is available. record = True try: result = rec.record_function(func, args, kw) finally: _outermost_call = True if not record: # If the method was not recorded, just call it. result = func(*args, **kw) return result # Mimic the actual function. _wrapper.__name__ = func.__name__ _wrapper.__doc__ = func.__doc__ _wrapper.__dict__.update(func.__dict__) return _wrapper apptools-4.1.0/apptools/scripting/package_globals.py0000644000175100001440000000075511674464005023667 0ustar ischnellusers00000000000000""" Globals for the scripting package. """ # Author: Prabhu Ramachandran # Copyright (c) 2008, Prabhu Ramachandran and Enthought, Inc. # License: BSD Style. # The global recorder. _recorder = None def get_recorder(): """Return the global recorder. Does not create a new one if none exists. """ global _recorder return _recorder def set_recorder(rec): """Set the global recorder instance. """ global _recorder _recorder = rec apptools-4.1.0/examples/0000755000175100001440000000000011674464005016163 5ustar ischnellusers00000000000000apptools-4.1.0/examples/permissions/0000755000175100001440000000000011674464005020536 5ustar ischnellusers00000000000000apptools-4.1.0/examples/permissions/server/0000755000175100001440000000000011674464005022044 5ustar ischnellusers00000000000000apptools-4.1.0/examples/permissions/server/server.py0000755000175100001440000004253211674464005023735 0ustar ischnellusers00000000000000#!/usr/bin/env python #------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Standard library imports. import errno import logging import os import shelve import SimpleXMLRPCServer import socket import sys import time # Log to stderr. logger = logging.getLogger() logger.addHandler(logging.StreamHandler()) logger.setLevel(logging.INFO) # The default IP address to listen on. DEFAULT_ADDR = socket.gethostbyname(socket.gethostname()) # The default port to listen on. DEFAULT_PORT = 3800 # The default data directory. DEFAULT_DATADIR = os.path.expanduser('~/.ets_perms_server') # The session timeout in seconds. SESSION_TIMEOUT = 90 * 60 class ServerImplementation(object): """This is a container for all the functions implemented by the server.""" def __init__(self, data_dir, insecure, local_user_db): """Initialise the object.""" self._data_dir = data_dir self._insecure = insecure self._local_user_db = local_user_db # Make sure we can call _close() at any time. self._keys = self._roles = self._assignments = self._blobs = \ self._users = None # Make sure the data directory exists. if not os.path.isdir(self._data_dir): os.mkdir(self._data_dir) # Load the data. self._keys = self._open_shelf('keys') self._roles = self._open_shelf('roles') self._assignments = self._open_shelf('assignments') self._blobs = self._open_shelf('blobs') if self._local_user_db: self._users = self._open_shelf('users') # Remove any expired session keys. now = time.time() keys = self._keys.keys() for k in keys: name, perm_ids, used_at = self._keys[k] if now - used_at >= SESSION_TIMEOUT: logger.info("Expiring session key for user %s" % name) del self._keys[k] self._sync(self._keys) def _close(self): """Close all the databases.""" if self._keys is not None: self._keys.close() self._keys = None if self._roles is not None: self._roles.close() self._roles = None if self._assignments is not None: self._assignments.close() self._assignments = None if self._blobs is not None: self._blobs.close() self._blobs = None if self._users is not None: if self._local_user_db: self._users.close() self._users = None def _open_shelf(self, name): """Open a shelf.""" logger.info("Loading %s" % name) fname = os.path.join(self._data_dir, name) try: return shelve.open(fname, writeback=True) except Exception, e: logger.error("Unable to open %s: %s" % (fname, e)) sys.exit(1) @staticmethod def _sync(shelf): try: shelf.sync() except Exception, e: logger.error("Unable to sync:" % e) Exception("An error occurred on the permissions server.") def _session_data(self, key): """Validate the session key and return the user name and list of permission ids.""" # Get the session details. try: session_name, perm_ids, used_at = self._keys[key] except KeyError: # Force the timeout test to fail. used_at = -SESSION_TIMEOUT # See if the session should be timed out. now = time.time() if now - used_at >= SESSION_TIMEOUT: try: del self._keys[key] except KeyError: pass self._sync(self._keys) raise Exception("Your session has timed out. Please login again.") # Update when the session was last used. self._keys[key] = session_name, perm_ids, now self._sync(self._keys) return session_name, perm_ids def _check_user(self, key, name): """Check that the session is current and the session user matches the given user and return the permission ids.""" session_name, perm_ids = self._session_data(key) if session_name != name: raise Exception("You do not have the appropriate authority.") return perm_ids def _check_authorisation(self, key, perm_id): """Check the user has the given permission.""" # Handle the easy cases first. if self._insecure or self.is_empty_policy(): return _, perm_ids = self._session_data(key) if perm_id not in perm_ids: raise Exception("You do not have the appropriate authority.") def _check_policy_authorisation(self, key): """Check that a policy management action is authorised.""" self._check_authorisation(key, 'ets.permissions.manage_policy') def _check_users_authorisation(self, key): """Check that a users management action is authorised.""" self._check_authorisation(key, 'ets.permissions.manage_users') def capabilities(self): """Return a list of capabilities that the implementation supports. The full list is 'user_password', 'user_add', 'user_modify', 'user_delete'. """ caps = ['user_password'] if self._local_user_db: caps.extend(['user_add', 'user_modify', 'user_delete']) return caps def add_user(self, name, description, password, key=None): """Add a new user.""" self._check_users_authorisation(key) if self._local_user_db: if self._users.has_key(name): raise Exception("The user \"%s\" already exists." % name) self._users[name] = (description, password) self._sync(self._users) else: raise Exception("Adding a user isn't supported.") # Return a non-None value. return True def authenticate_user(self, name, password): """Return the tuple of the user name, description, and blob if the user was successfully authenticated.""" if self._local_user_db: try: description, pword = self._users[name] except KeyError: raise Exception("The name or password is invalid.") if password != pword: raise Exception("The name or password is invalid.") else: # FIXME raise Exception("Authenticating a user isn't yet supported.") # Create the session key. The only reason for using a human readable # string is to make the test client easier to use. We only make # limited attempts at creating a unique key. for i in range(5): key = '' for ch in os.urandom(16): key += '%02x' % ord(ch) if not self._keys.has_key(key): break else: # Something is seriously wrong if we get here. msg = "Unable to create unique session key." logger.error(msg) raise Exception(msg) # Get the user's permissions. perm_ids = [] for r in self._assignments.get(name, []): _, perms = self._roles[r] perm_ids.extend(perms) # Save the session data. self._keys[key] = name, perm_ids, time.time() return key, name, description, self._blobs.get(name, {}) def matching_users(self, name, key=None): """Return the full name and description of all the users that match the given name.""" self._check_users_authorisation(key) if self._local_user_db: # Get any user that starts with the name. users = [(full_name, description) for full_name, (description, _) in self._users.items() if full_name.startswith(name)] users = sorted(users) else: # FIXME raise Exception("Searching for users isn't yet supported.") return users def modify_user(self, name, description, password, key=None): """Update the description and password for the given user.""" self._check_users_authorisation(key) if self._local_user_db: if not self._users.has_key(name): raise Exception("The user \"%s\" does not exist." % name) self._users[name] = (description, password) self._sync(self._users) else: raise Exception("Modifying a user isn't supported.") # Return a non-None value. return True def delete_user(self, name, key=None): """Delete a user.""" self._check_users_authorisation(key) if self._local_user_db: try: del self._users[name] except KeyError: raise UserStorageError("The user \"%s\" does not exist." % name) self._sync(self._users) else: raise Exception("Deleting a user isn't supported.") # Return a non-None value. return True def unauthenticate_user(self, key=None): """Unauthenticate the given user (ie. identified by the session key). """ if not self._local_user_db: # FIXME: LDAP may or may not need anything here. raise Exception("Unauthenticating a user isn't yet supported.") # Invalidate any session key: if key is not None: try: del self._keys[key] except KeyError: pass self._sync(self._keys) # Return a non-None value. return True def update_blob(self, name, blob, key=None): """Update the blob for the given user.""" self._check_user(key, name) self._blobs[name] = blob self._sync(self._blobs) # Return a non-None value. return True def update_password(self, name, password, key=None): """Update the password for the given user.""" self._check_user(key, name) if not self._local_user_db: try: description, _ = self._users[name] except KeyError: raise Exception("The user \"%s\" does not exist." % name) self._users[name] = (description, password) self._sync(self._users) else: # FIXME raise Exception("Updating a user password isn't yet supported.") # Return a non-None value. return True def add_role(self, name, description, perm_ids, key=None): """Add a new role.""" self._check_policy_authorisation(key) if self._roles.has_key(name): raise Exception("The role \"%s\" already exists." % name) self._roles[name] = (description, perm_ids) self._sync(self._roles) # Return a non-None value. return True def all_roles(self, key=None): """Return a list of all roles.""" self._check_policy_authorisation(key) return [(name, description) for name, (description, _) in self._roles.items()] def delete_role(self, name, key=None): """Delete a role.""" self._check_policy_authorisation(key) if not self._roles.has_key(name): raise Exception("The role \"%s\" does not exist." % name) del self._roles[name] # Remove the role from any users who have it. for user, role_names in self._assignments.items(): try: role_names.remove(name) except ValueError: continue self._assignments[user] = role_names self._sync(self._roles) self._sync(self._assignments) # Return a non-None value. return True def get_assignment(self, user_name, key=None): """Return the details of the assignment for the given user name.""" self._check_policy_authorisation(key) try: role_names = self._assignments[user_name] except KeyError: return '', [] return user_name, role_names def get_policy(self, name, key=None): """Return the details of the policy for the given user name.""" return name, self._check_user(key, name) def is_empty_policy(self): """Return True if there is no useful data.""" empty = (len(self._roles) == 0 or len(self._assignments) == 0) # Include the users as well if the database is local. if self._local_user_db and len(self._users) == 0: empty = True return empty def matching_roles(self, name, key=None): """Return the full name, description and permissions of all the roles that match the given name.""" self._check_policy_authorisation(key) # Return any role that starts with the name. roles = [(full_name, description, perm_ids) for full_name, (description, perm_ids) in self._roles.items() if full_name.startswith(name)] return sorted(roles) def modify_role(self, name, description, perm_ids, key=None): """Update an existing role.""" self._check_policy_authorisation(key) if not self._roles.has_key(name): raise Exception("The role \"%s\" does not exist." % name) self._roles[name] = (description, perm_ids) self._sync(self._roles) # Return a non-None value. return True def set_assignment(self, user_name, role_names, key=None): """Save the roles assigned to a user.""" self._check_policy_authorisation(key) if len(role_names) == 0: # Delete the user, but don't worry if there is no current # assignment. try: del self._assignments[user_name] except KeyError: pass else: self._assignments[user_name] = role_names self._sync(self._assignments) # Return a non-None value. return True class RPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer): """A thin wrapper around SimpleXMLRPCServer that handles its initialisation.""" def __init__(self, addr=DEFAULT_ADDR, port=DEFAULT_PORT, data_dir=DEFAULT_DATADIR, insecure=False, local_user_db=False): """Initialise the object.""" SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, (addr, port)) self._impl = ServerImplementation(data_dir, insecure, local_user_db) self.register_instance(self._impl) def server_close(self): """Reimplemented to tidy up the implementation.""" SimpleXMLRPCServer.SimpleXMLRPCServer.server_close(self) self._impl._close() if __name__ == '__main__': # Parse the command line. import optparse p = optparse.OptionParser(description="This is an XML-RPC server that " "provides user, role and permissions data to a user and policy " "manager that is part of the ETS Permissions Framework.") p.add_option('--data-dir', default=DEFAULT_DATADIR, dest='data_dir', metavar="DIR", help="the server's data directory [default: %s]" % DEFAULT_DATADIR) p.add_option('--insecure', action='store_true', default=False, dest='insecure', help="don't require a session key for data changes") p.add_option('--ip-address', default=DEFAULT_ADDR, dest='addr', help="the IP address to listen on [default: %s]" % DEFAULT_ADDR) p.add_option('--local-user-db', action='store_true', default=False, dest='local_user_db', help="use a local user database instead of an LDAP directory") p.add_option('--port', type='int', default=DEFAULT_PORT, dest='port', help="the TCP port to listen on [default: %d]" % DEFAULT_PORT) opts, args = p.parse_args() if args: p.error("unexpected additional arguments: %s" % " ".join(args)) # We need a decent RNG for session keys. if not opts.insecure: try: os.urandom(1) except AttributeError, NotImplementedError: sys.stderr.write("os.urandom() isn't implemented so the --insecure flag must be used\n") sys.exit(1) # FIXME: Add LDAP support. if not opts.local_user_db: sys.stderr.write("Until LDAP support is implemented use the --local-user-db flag\n") sys.exit(1) # Create and start the server. server = RPCServer(addr=opts.addr, port=opts.port, data_dir=opts.data_dir, insecure=opts.insecure, local_user_db=opts.local_user_db) if opts.insecure: logger.warn("Server starting in insecure mode") else: logger.info("Server starting") try: try: server.serve_forever() except KeyboardInterrupt: pass else: raise finally: server.server_close() logger.info("Server terminated") apptools-4.1.0/examples/permissions/server/enthought/0000755000175100001440000000000011674464005024051 5ustar ischnellusers00000000000000apptools-4.1.0/examples/permissions/server/enthought/permissions/0000755000175100001440000000000011674464005026424 5ustar ischnellusers00000000000000apptools-4.1.0/examples/permissions/server/enthought/permissions/external/0000755000175100001440000000000011674464005030246 5ustar ischnellusers00000000000000apptools-4.1.0/examples/permissions/server/enthought/permissions/external/policy_storage.py0000644000175100001440000000771211674464005033652 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from apptools.permissions.default.api import IPolicyStorage, \ PolicyStorageError from traits.api import HasTraits, implements # Local imports. from proxy_server import ProxyServer class PolicyStorage(HasTraits): """This implements a policy database accessed via XML RPC.""" implements(IPolicyStorage) ########################################################################### # 'IPolicyStorage' interface. ########################################################################### def add_role(self, name, description, perm_ids): """Add a new role.""" try: ProxyServer.add_role(name, description, perm_ids, ProxyServer.key) except Exception, e: raise PolicyStorageError(ProxyServer.error(e)) def all_roles(self): """Return a list of all roles.""" try: return ProxyServer.all_roles(ProxyServer.key) except Exception, e: raise PolicyStorageError(ProxyServer.error(e)) def delete_role(self, name): """Delete a role.""" try: ProxyServer.delete_role(name, ProxyServer.key) except Exception, e: raise PolicyStorageError(ProxyServer.error(e)) def get_assignment(self, user_name): """Return the details of the assignment for the given user name.""" try: return ProxyServer.get_assignment(user_name, ProxyServer.key) except Exception, e: raise PolicyStorageError(ProxyServer.error(e)) def get_policy(self, name): """Return the details of the policy for the given user name.""" description, blob, perm_ids = ProxyServer.cache if ProxyServer.key is not None: try: name, perm_ids = ProxyServer.get_policy(name, ProxyServer.key) except Exception, e: raise PolicyStorageError(ProxyServer.error(e)) # Save the permissions ids in the persistent cache. ProxyServer.cache = description, blob, perm_ids try: ProxyServer.write_cache() except: pass return name, perm_ids def is_empty(self): """See if the database is empty.""" try: return ProxyServer.is_empty_policy() except Exception, e: raise PolicyStorageError(ProxyServer.error(e)) def matching_roles(self, name): """Return the full name, description and permissions of all the roles that match the given name.""" try: return ProxyServer.matching_roles(name, ProxyServer.key) except Exception, e: raise PolicyStorageError(ProxyServer.error(e)) def modify_role(self, name, description, perm_ids): """Update the description and permissions for the given role.""" try: ProxyServer.modify_role(name, description, perm_ids, ProxyServer.key) except Exception, e: raise PolicyStorageError(ProxyServer.error(e)) def set_assignment(self, user_name, role_names): """Save the roles assigned to a user.""" try: ProxyServer.set_assignment(user_name, role_names, ProxyServer.key) except Exception, e: raise PolicyStorageError(ProxyServer.error(e)) apptools-4.1.0/examples/permissions/server/enthought/permissions/external/__init__.py0000644000175100001440000000046311674464005032362 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007 by Enthought, Inc. # All rights reserved. #------------------------------------------------------------------------------ try: __import__('pkg_resources').declare_namespace(__name__) except: pass apptools-4.1.0/examples/permissions/server/enthought/permissions/external/user_storage.py0000644000175100001440000001363411674464005033331 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Standard library imports. import errno import socket # Enthought library imports. from apptools.permissions.default.api import IUserStorage, UserStorageError from traits.api import HasTraits, implements, List, Str # Local imports. from proxy_server import ProxyServer class UserStorage(HasTraits): """This implements a user database accessed via XML RPC.""" implements(IUserStorage) #### 'IUserStorage' interface ############################################# capabilities = List(Str) ########################################################################### # 'IUserStorage' interface. ########################################################################### def add_user(self, name, description, password): """Add a new user.""" try: ProxyServer.add_user(name, description, password, ProxyServer.key) except Exception, e: raise UserStorageError(ProxyServer.error(e)) def authenticate_user(self, name, password): """Return the tuple of the user name, description, and blob if the user was successfully authenticated.""" try: key, name, description, blob = ProxyServer.authenticate_user(name, password) # We don't save the cache because we should be about to read the # real permission ids and we do it then. ProxyServer.cache = description, blob, [] except Exception, e: # See if we couldn't connect to the server. if not isinstance(e, socket.error): raise UserStorageError(ProxyServer.error(e)) err, _ = e.args if err != errno.ECONNREFUSED: raise UserStorageError(ProxyServer.error(e)) try: ok = ProxyServer.read_cache() except Exception, e: raise UserStorageError(str(e)) if not ok: raise UserStorageError(ProxyServer.error(e)) # We are in "disconnect" mode. key = None description, blob, _ = ProxyServer.cache ProxyServer.key = key return name, description, blob def delete_user(self, name): """Delete a new user.""" try: ProxyServer.delete_user(name, ProxyServer.key) except Exception, e: raise UserStorageError(ProxyServer.error(e)) def is_empty(self): """See if the database is empty.""" # We leave it to the policy storage to answer this question. return False def matching_users(self, name): """Return the full name and description of all the users that match the given name.""" try: return ProxyServer.matching_users(name, ProxyServer.key) except Exception, e: raise UserStorageError(ProxyServer.error(e)) def modify_user(self, name, description, password): """Update the description and password for the given user.""" try: ProxyServer.modify_user(name, description, password, ProxyServer.key) except Exception, e: raise UserStorageError(ProxyServer.error(e)) def unauthenticate_user(self, user): """Unauthenticate the given user.""" if ProxyServer.key is None: ok = True else: try: ok = ProxyServer.unauthenticate_user(ProxyServer.key) except Exception, e: raise UserStorageError(ProxyServer.error(e)) if ok: ProxyServer.key = '' ProxyServer.cache = None return ok def update_blob(self, name, blob): """Update the blob for the given user.""" # Update the cache. description, _, perm_ids = ProxyServer.cache ProxyServer.cache = description, blob, perm_ids if ProxyServer.key is None: # Write the cache and tell the user about any errors. ProxyServer.write_cache() else: try: ProxyServer.update_blob(name, blob, ProxyServer.key) except Exception, e: raise UserStorageError(ProxyServer.error(e)) # Write the cache but ignore any errors. try: ProxyServer.write_cache() except: pass def update_password(self, name, password): """Update the password for the given user.""" # If the remote server disappeared after the capabilities were read but # before the user was authenticated then we could get here. if ProxyServer.key is None: raise UserStorageError("It is not possible to change password " "when disconnected from the permissions server.") try: ProxyServer.update_password(name, password, ProxyServer.key) except Exception, e: raise UserStorageError(ProxyServer.error(e)) ########################################################################### # Trait handlers. ########################################################################### def _capabilities_default(self): """Return the storage capabilities.""" try: caps = ProxyServer.capabilities() except: caps = [] return caps apptools-4.1.0/examples/permissions/server/enthought/permissions/external/proxy_server.py0000644000175100001440000001056111674464005033372 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Standard library imports. import cPickle as pickle import errno import os import socket import xmlrpclib # Enthought library imports. from traits.etsconfig.api import ETSConfig from pyface.api import confirm, YES # The default IP address to connect to. DEFAULT_ADDR = socket.gethostbyname(socket.gethostname()) # The default port to connect to. DEFAULT_PORT = 3800 class ProxyServer(xmlrpclib.ServerProxy): """This is a thin wrapper around xmlrpclib.ServerProxy that handles the server address and error reporting.""" # The name of the user data cache file. _cache_file = os.path.join(ETSConfig.application_home, 'ets_perms_user_cache') def __init__(self): """Initialise the object. The server address and TCP/IP port are taken from the ETS_PERMS_SERVER environment variable if set.""" self._server = os.environ.get('ETS_PERMS_SERVER', '%s:%d' % (DEFAULT_ADDR, DEFAULT_PORT)) xmlrpclib.ServerProxy.__init__(self, uri='http://%s' % self._server) # The session key. It is an empty string if the user is not # authenticated and None if the user is in "disconnected" mode. self.key = '' # The user data cache. This is a tuple of the user description, blob # and list of permission ids when the session key is not an empty # string. self.cache = None def write_cache(self): """Write the user cache to persistent storage.""" f = open(self._cache_file, 'w') pickle.dump(self.cache, f) f.close() def read_cache(self): """Read the user cache from persistent storage. Returns False if there was no cache to read.""" try: f = open(self._cache_file, 'r') try: if confirm(None, "It was not possible to connect to the " "permissions server. Instead you can use the settings " "used when you last logged in from this system. If " "you do this then any changes made to settings " "normally held on the permissions server will be lost " "when you next login successfully.\n\nDo you want to " "use the saved settings?") != YES: raise Exception("") try: self.cache = pickle.load(f) except: raise Exception("Unable to read %s." % self._cache_file) finally: f.close() except IOError, e: if e.errno == errno.ENOENT: # There is no cache file. return False raise Exception("Unable to open %s: %s." % (self._cache_file, e)) return True def error(self, e): """Return a user friendly string describing the given exception.""" if isinstance(e, socket.error): err, msg = e.args if err == errno.ECONNREFUSED: emsg = "Unable to connect to permissions server at %s." % self._server else: emsg = "Socket error to permissions server at %s." % self._server elif isinstance(e, xmlrpclib.Fault): # Extract the text of the exception. If we don't recognise the # format then display the lot. tail = e.faultString.find(':') if tail < 0: emsg = "Unexpected exception from permissions server at %s: %s\n" % (self._server, e.faultString) else: emsg = e.faultString[tail + 1:] else: # Let any existing error handling deal with it. raise e return emsg # Create a singleton. ProxyServer = ProxyServer() apptools-4.1.0/examples/permissions/server/enthought/permissions/__init__.py0000644000175100001440000000046311674464005030540 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007 by Enthought, Inc. # All rights reserved. #------------------------------------------------------------------------------ try: __import__('pkg_resources').declare_namespace(__name__) except: pass apptools-4.1.0/examples/permissions/server/enthought/__init__.py0000644000175100001440000000000011674464005026150 0ustar ischnellusers00000000000000apptools-4.1.0/examples/permissions/server/setup.py0000644000175100001440000000073611674464005023564 0ustar ischnellusers00000000000000# Major package imports. from setuptools import setup, find_packages setup( name = 'PermissionsServer', version = '1.0', author = 'Riverbank Computing Limited', author_email = 'info@riverbankcomputing.com', license = 'BSD', zip_safe = True, packages = find_packages(), include_package_data = True, namespace_packages = [ 'enthought' ], ) apptools-4.1.0/examples/permissions/server/test_client.py0000755000175100001440000001673611674464005024753 0ustar ischnellusers00000000000000#!/usr/bin/env python #------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Standard library imports. import errno import optparse import socket import sys import xmlrpclib # The default IP address to connect to. DEFAULT_ADDR = socket.gethostbyname(socket.gethostname()) # The default port to connect to. DEFAULT_PORT = 3800 def check_blob(opts): """Check the blob was specified with the --blob flag and return the value. """ if not opts.blob: raise Exception("the --blob flag to specify a blob") return opts.blob def check_name(opts): """Check the name was specified with the --name flag and return the value. """ if not opts.name: raise Exception("the --name flag to specify a name") return opts.name def check_password(opts): """Check the password was specified with the --password flag and return the value.""" if not opts.password: raise Exception("the --password flag to specify a password") return opts.password def add_role(opts): """Add a new role.""" return [check_name(opts), opts.description, opts.permissions, opts.key] def add_user(opts): """Add a new user.""" return [check_name(opts), opts.description, check_password(opts), opts.key] def all_roles(opts): """Return all roles.""" return [opts.key] def authenticate_user(opts): """Authenticate a user and return their details.""" return [check_name(opts), check_password(opts)] def delete_role(opts): """Delete a role.""" return [check_name(opts), opts.key] def delete_user(opts): """Delete a user.""" return [check_name(opts), opts.key] def capabilities(opts): """Get the capabilities of the server.""" return [] def matching_roles(opts): """Get the roles that match a name.""" return [opts.name, opts.key] def matching_users(opts): """Get the users that match a name.""" return [opts.name, opts.key] def get_assignment(opts): """Get the details of a user's assignment.""" return [check_name(opts), opts.key] def get_policy(opts): """Get the details of a user's policy.""" return [check_name(opts), opts.key] def is_empty_policy(opts): """Check if there is any policy data.""" return [] def set_assignment(opts): """Save the details of a user's assignment.""" return [check_name(opts), opts.roles, opts.key] def unauthenticate_user(opts): """Unauthenticate a user.""" return [opts.key] def modify_role(opts): """Modify an existing role.""" return [check_name(opts), opts.description, opts.permissions, opts.key] def modify_user(opts): """Modify an existing user.""" return [check_name(opts), opts.description, check_password(opts), opts.key] def update_blob(opts): """Update the blob for a user.""" return [check_name(opts), check_blob(opts)] def update_password(opts): """Update the password for a user.""" return [check_name(opts), check_password(opts)] # The list of actions that can be invoked from the command line. actions = [ add_role, add_user, all_roles, authenticate_user, capabilities, delete_role, delete_user, get_assignment, get_policy, is_empty_policy, matching_roles, matching_users, modify_role, modify_user, set_assignment, unauthenticate_user, update_blob, update_password, ] def store_list(option, opt_str, value, p): """An option parser callback that converts space separated words into a list of strings.""" setattr(p.values, option.dest, value.split(' ')) # Parse the command line. p = optparse.OptionParser(usage="%prog [options] action", description="This " "is a client used to test the XML-RPC server. The following actions " "are supported: %s" % ', '.join([a.func_name for a in actions])) p.add_option('--ip-address', default=DEFAULT_ADDR, dest='addr', help="the IP address to connect to [default: %s]" % DEFAULT_ADDR) p.add_option('-b', '--blob', dest='blob', help="a blob string (used by update_blob)") p.add_option('-d', '--description', default='', dest='description', help="a description (used by add_role, add_user, modify_role, " "modify_user)") p.add_option('-k', '--key', default='', dest='key', help="the session key returned by login (used by add_role, add_user, " "all_roles, delete_role, delete_user, get_assignment, " "get_policy, modify_role, modify_user, set_assignment, " "unauthenticate_user)") p.add_option('-n', '--name', default='', dest='name', help="a name (used by add_role, add_user, authenticate_user, " "delete_role, delete_user, get_assignment, get_policy, " "matching_roles, matching_users, modify_role, modify_user, " "set_assignment, update_blob, update_user)") p.add_option('--permissions', action='callback', callback=store_list, default=[], dest='permissions', type='string', help="a space separated list of permission names (used by add_role, " "modify_role)") p.add_option('-p', '--password', dest='password', help="a password (used by add_user, authenticate_user, modify_user " "update_password)") p.add_option('--port', type='int', default=DEFAULT_PORT, dest='port', help="the TCP port to connect to [default: %d]" % DEFAULT_PORT) p.add_option('--roles', action='callback', callback=store_list, default=[], dest='roles', type='string', help="a space separated list of role names (used by set_assignment)") p.add_option('-v', '--verbose', action='store_true', default=False, dest='verbose', help="display the progress of the RPC") opts, args = p.parse_args() if len(args) != 1: p.error("exactly one action must be given") for action in actions: if action.func_name == args[0]: break else: p.error("unknown action: %s" % args[0]) # Get the action's arguments. try: action_args = action(opts) except Exception, e: sys.stderr.write("The %s action requires %s\n" % (action.func_name, e)) sys.exit(2) # Create the proxy. proxy = xmlrpclib.ServerProxy(uri='http://%s:%d' % (opts.addr, opts.port), verbose=opts.verbose) try: result = getattr(proxy, action.func_name)(*action_args) except socket.error, e: err, msg = e.args if err == errno.ECONNREFUSED: sys.stderr.write("Unable to connect to permissions server at %s:%d\n" % (opts.addr, opts.port)) else: sys.stderr.write("socket error: %s\n" % msg) sys.exit(1) except xmlrpclib.Fault, e: # Extract the text of the exception. If we don't recognise the format then # display the lot. tail = e.faultString.find(':') if tail < 0: msg = e.faultString else: msg = e.faultString[tail + 1:] print "The call raised an exception: %s" % msg sys.exit(0) # Show the result. print "Result:", result apptools-4.1.0/examples/permissions/application/0000755000175100001440000000000011674464005023041 5ustar ischnellusers00000000000000apptools-4.1.0/examples/permissions/application/person.py0000644000175100001440000000227511674464005024727 0ustar ischnellusers00000000000000"""A simple example of an object model.""" # Enthought library imports. from apptools.permissions.api import SecureHandler from traits.api import HasTraits, Int, Unicode from traitsui.api import Item, View # Local imports. from permissions import UpdatePersonAgePerm, PersonSalaryPerm class Person(HasTraits): """A simple example of an object model""" # Name. name = Unicode # Age in years. age = Int # Salary. salary = Int # Define the default view with permissions attached. age_perm = UpdatePersonAgePerm salary_perm = PersonSalaryPerm traits_view = View( Item(name='name'), Item(name='age', enabled_when='object.age_perm.granted'), Item(name='salary', visible_when='object.salary_perm.granted'), handler=SecureHandler) ########################################################################### # 'object' interface. ########################################################################### def __str__(self): """Return an informal string representation of the object.""" return self.name #### EOF ###################################################################### apptools-4.1.0/examples/permissions/application/secured_debug_view.py0000644000175100001440000000146211674464005027250 0ustar ischnellusers00000000000000"""The secured DebugView for the permissions framework example.""" # Enthought library imports. from pyface.workbench.debug.api import DebugView from apptools.permissions.api import SecureProxy # Local imports. from permissions import DebugViewPerm class SecuredDebugView(DebugView): """A secured DebugView.""" ########################################################################### # 'IWorkbenchPart' interface. ########################################################################### def create_control(self, parent): """Reimplemented to secure the created control.""" control = DebugView.create_control(self, parent) return SecureProxy(control, permissions=[DebugViewPerm]) #### EOF ###################################################################### apptools-4.1.0/examples/permissions/application/workbench_window.py0000644000175100001440000001065211674464005026770 0ustar ischnellusers00000000000000"""The workbench window for the permissions framework example.""" # Enthought library imports. from pyface.action.api import Action, Group, MenuManager from pyface.workbench.api import EditorManager, WorkbenchWindow from pyface.workbench.api import Perspective, PerspectiveItem from pyface.workbench.action.api import MenuBarManager from pyface.workbench.action.api import ToolBarManager from pyface.workbench.action.api import ViewMenuManager from apptools.permissions.api import SecureProxy from apptools.permissions.action.api import UserMenuManager from traits.api import Callable, HasTraits, List, Instance # Local imports. from permissions import NewPersonPerm from person import Person from toolkit_editor import ToolkitEditor class ExampleEditorManager(EditorManager): """An editor manager that supports the editor memento protocol.""" ####################################################################### # 'IEditorManager' interface. ####################################################################### def create_editor(self, window, obj, kind): """Reimplemented to create an editor appropriate to the object being edited. """ if isinstance(obj, HasTraits): # The superclass handles Traits objects. editor = super(ExampleEditorManager, self).create_editor(window, obj, kind) else: # Assume it is handled by a toolkit specific editor. editor = ToolkitEditor(window=window, obj=obj) return editor class ExampleWorkbenchWindow(WorkbenchWindow): """A simple example of using the workbench window.""" #### 'WorkbenchWindow' interface ########################################## # The available perspectives. perspectives = [ Perspective( name = 'Foo', contents = [ PerspectiveItem(id='Black', position='bottom'), PerspectiveItem(id='Debug', position='left') ] ), Perspective( name = 'Bar', contents = [ PerspectiveItem(id='Debug', position='left') ] ) ] #### Private interface #################################################### # The Exit action. _exit_action = Instance(Action) # The New Person action. _new_person_action = Instance(Action) ########################################################################### # 'ApplicationWindow' interface. ########################################################################### def _editor_manager_default(self): """ Trait initializer. Here we return the replacement editor manager. """ return ExampleEditorManager() def _menu_bar_manager_default(self): """Trait initializer.""" file_menu = MenuManager(self._new_person_action, self._exit_action, name='&File', id='FileMenu') view_menu = ViewMenuManager(name='&View', id='ViewMenu', window=self) user_menu = UserMenuManager(id='UserMenu', window=self) return MenuBarManager(file_menu, view_menu, user_menu, window=self) def _tool_bar_manager_default(self): """Trait initializer.""" return ToolBarManager(self._exit_action, show_tool_names=False) ########################################################################### # 'WorkbenchWindow' interface. ########################################################################### def _views_default(self): """Trait initializer.""" from secured_debug_view import SecuredDebugView return [SecuredDebugView(window=self)] ########################################################################### # Private interface. ########################################################################### def __exit_action_default(self): """Trait initializer.""" return Action(name='E&xit', on_perform=self.workbench.exit) def __new_person_action_default(self): """Trait initializer.""" # Create the action and secure it with the appropriate permission. act = Action(name='New Person', on_perform=self._new_person) act = SecureProxy(act, permissions=[NewPersonPerm]) return act def _new_person(self): """Create a new person.""" self.workbench.edit(Person(name='New', age=100)) #### EOF ###################################################################### apptools-4.1.0/examples/permissions/application/permissions.py0000644000175100001440000000157111674464005025772 0ustar ischnellusers00000000000000"""The definitions of all the permissions used in the example.""" from apptools.permissions.api import Permission # Access to the Debug view. DebugViewPerm = Permission(id='ets.permissions.example.debug.view', description=u"Use the debug view") # Add a new person. NewPersonPerm = Permission(id='ets.permissions.example.person.new', description=u"Add a new person") # Update a person's age. UpdatePersonAgePerm = Permission(id='ets.permissions.example.person.age.update', description=u"Update a person's age") # View or update a person's salary. PersonSalaryPerm = Permission(id='ets.permissions.example.person.salary', description=u"View or update a person's salary") # Enable the example toolkit specific widget. EnableWidgetPerm = Permission(id='ets.permissions.example.widget', description=u"Enable the example toolkit specific widget") apptools-4.1.0/examples/permissions/application/toolkit_editor.py0000644000175100001440000000352711674464005026455 0ustar ischnellusers00000000000000"""An editor implemented by a toolkit specific widget. This demonstrates the ability to apply permissions to toolkit specific widgets. """ # Enthought library imports. from traits.etsconfig.api import ETSConfig from apptools.permissions.api import SecureProxy from pyface.workbench.api import Editor # Local imports. from permissions import EnableWidgetPerm class ToolkitEditor(Editor): """A workbench editor that displays string with a permission attached to the toolkit widget. """ def create_control(self, parent): """Create the toolkit specific control.""" tk = ETSConfig.toolkit if tk == 'wx': import wx control = wx.StaticText(parent, -1, self.obj, style=wx.ALIGN_CENTER) elif tk == 'qt4': from PyQt4 import QtCore, QtGui control = QtGui.QLabel(self.obj, parent) control.setAlignment(QtCore.Qt.AlignHCenter) else: raise ValueError, "unsupported toolkit: %s" % tk # Return the wrapped control. return SecureProxy(control, [EnableWidgetPerm]) def destroy_control(self): """Create the toolkit specific control.""" tk = ETSConfig.toolkit if tk == 'wx': self.control.Destroy() elif tk == 'qt4': self.control.setParent(None) def _name_default(self): """Show the toolkit in the editor name.""" return str(self) ########################################################################### # 'object' interface. ########################################################################### def __str__(self): """Return an informal string representation of the object.""" return ETSConfig.toolkit + " Editor" #### EOF ###################################################################### apptools-4.1.0/examples/permissions/application/example.py0000644000175100001440000000431311674464005025047 0ustar ischnellusers00000000000000"""A simple example of using the permissions framework.""" # Standard library imports. import logging # Enthought library imports. from pyface.api import GUI, YES from pyface.workbench.api import Workbench # Local imports. from workbench_window import ExampleWorkbenchWindow from person import Person # Log to stderr. logger = logging.getLogger() logger.addHandler(logging.StreamHandler()) logger.setLevel(logging.DEBUG) class ExampleWorkbench(Workbench): """A simple example of using the workbench.""" #### 'Workbench' interface ################################################ # The factory (in this case simply a class) that is used to create # workbench windows. window_factory = ExampleWorkbenchWindow ########################################################################### # Private interface. ########################################################################### def _exiting_changed(self, event): """Called when the workbench is exiting.""" if self.active_window.confirm('Ok to exit?') != YES: event.veto = True def main(argv): """A simple example of using the workbench.""" # Create the GUI. gui = GUI() # Create the workbench. # # fixme: I wouldn't really want to specify the state location here. # Ideally this would be part of the GUI's as DOMs idea, and the state # location would be an attribute picked up from the DOM hierarchy. This # would also be the mechanism for doing 'confirm' etc... Let the request # bubble up the DOM until somebody handles it. workbench = ExampleWorkbench(state_location=gui.state_location) # Create the workbench window. window = workbench.create_window(position=(300, 300), size=(800, 600)) window.open() # This will cause a TraitsUI editor to be created. window.edit(Person(name='fred', age=42, salary=50000)) # This will cause a toolkit specific editor to be created. window.edit("This text is implemented by a toolkit specific widget.") # Start the GUI event loop. gui.start_event_loop() if __name__ == '__main__': import sys; main(sys.argv) #### EOF ###################################################################### apptools-4.1.0/examples/preferences/0000755000175100001440000000000011674464005020464 5ustar ischnellusers00000000000000apptools-4.1.0/examples/preferences/example.ini0000644000175100001440000000011611674464005022616 0ustar ischnellusers00000000000000[acme] width = 99 height = 600 [acme.workbench] bgcolor = red fgcolor = blue apptools-4.1.0/examples/preferences/preferences_manager.py0000644000175100001440000000556111674464005025040 0ustar ischnellusers00000000000000""" An example of using the preferences manager. """ # Enthought library imports. from traits.api import Color, Int, Float, Str from traitsui.api import View # Local imports. from apptools.preferences.api import Preferences, PreferencesHelper from apptools.preferences.api import get_default_preferences from apptools.preferences.api import set_default_preferences from apptools.preferences.ui.api import PreferencesManager, PreferencesPage # Create a preferences collection from a file and make it the default root # preferences node for all preferences helpers etc. set_default_preferences(Preferences(filename='example.ini')) class AcmePreferencesPage(PreferencesPage): """ A preference page for the Acme preferences. """ #### 'IPreferencesPage' interface ######################################### # The page's category (e.g. 'General/Appearence'). The empty string means # that this is a top-level page. category = '' # The page's help identifier (optional). If a help Id *is* provided then # there will be a 'Help' button shown on the preference page. help_id = '' # The page name (this is what is shown in the preferences dialog. name = 'Acme' # The path to the preferences node that contains our preferences. preferences_path = 'acme' #### Preferences ########################################################## width = Int(800) height = Int(600) #### Traits UI views ###################################################### view = View('width', 'height') class AcmeWorkbenchPreferencesPage(PreferencesPage): """ A preference page for the Acme workbench preferences. """ #### 'IPreferencesPage' interface ######################################### # The page's category (e.g. 'General/Appearence'). The empty string means # that this is a top-level page. category = 'Acme' # The page's help identifier (optional). If a help Id *is* provided then # there will be a 'Help' button shown on the preference page. help_id = '' # The page name (this is what is shown in the preferences dialog. name = 'Workbench' # The path to the preferences node that contains our preferences. preferences_path = 'acme' #### Preferences ########################################################## bgcolor = Color fgcolor = Color #### Traits UI views ###################################################### view = View('bgcolor', 'fgcolor') # Entry point. if __name__ == '__main__': # Create a manager with some pages. preferences_manager = PreferencesManager( pages = [ AcmePreferencesPage(), AcmeWorkbenchPreferencesPage() ] ) # Show the UI... preferences_manager.configure_traits() # Save the preferences... get_default_preferences().flush() #### EOF ###################################################################### apptools-4.1.0/examples/appscripting/0000755000175100001440000000000011674464005020666 5ustar ischnellusers00000000000000apptools-4.1.0/examples/appscripting/model.py0000644000175100001440000000364011674464005022343 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import Enum, HasTraits, Int, Unicode from apptools.appscripting.api import scriptable, Scriptable class Label(HasTraits): """ The Label class implements the data model for a label. """ #### 'Label' interface #################################################### # The name. name = Unicode # The size in points. size = Int(18) # The style. style = Scriptable(Enum('normal', 'bold', 'italic')) ########################################################################### # 'Label' interface. ########################################################################### @scriptable def __init__(self, **traits): """ Initialise the object. We only implement this so that it can be decorated and so the script manager knows how to recreate it. """ super(Label, self).__init__(**traits) @scriptable def increment_size(self, by): """ Increment the current font size. This demonstrates a scriptable method. """ self.size += by @scriptable def decrement_size(self, by): """ decrement the current font size. This demonstrates a scriptable method. """ self.size -= by apptools-4.1.0/examples/appscripting/actions.py0000644000175100001440000001640211674464005022703 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import Any, Instance, Str, Unicode from pyface.action.api import Action from pyface.workbench.api import WorkbenchWindow # Local imports. from model import Label class BoundAction(Action): """An action with a bound object. The action is automatically disabled if the bound object is None.""" #### 'BoundAction' interface ############################################## # The bound object. obj = Any # The optional trait on obj that we are synch'ed with. trait_name = Str ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **traits): """Initialise the object.""" super(BoundAction, self).__init__(**traits) # Fake an obj change to set the initial state. self._obj_changed(None, self.obj) ########################################################################### # Traits handlers. ########################################################################### def _obj_changed(self, old, new): """Invoked when the bound object changes.""" if old is not None: if self.trait_name: # Ignore any changes to the old object. old.on_trait_change(self._trait_changed, self.trait_name, remove=True) enabled = False if new is not None: if self.trait_name: # Check for any changes on the new object. new.on_trait_change(self._trait_changed, self.trait_name) # Get the current state. if getattr(new, self.trait_name): enabled = True else: enabled = True self.enabled = enabled def _trait_changed(self, new): """Invoked when the trait on the bound object changes.""" self.enabled = new class BoundWorkbenchAction(BoundAction): """A bound action whose object is being edited in a workbench editor. The action is automatically rebound when the active editor changes.""" #### 'BoundWorkbenchAction' interface ##################################### # The type of the object that we will be enabled for. If it is None then # we will be enabled for all types. trait_type = Any # The workbench window containing the action. window = Instance(WorkbenchWindow) ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **traits): """Initialise the object.""" super(BoundWorkbenchAction, self).__init__(**traits) self.window.on_trait_change(self._editor_changed, 'active_editor') # Fake an editor change to set the initial state. self._editor_changed(self.window.active_editor) ########################################################################### # Traits handlers. ########################################################################### def _editor_changed(self, new): """Invoked when the active editor changes.""" obj = None if new is not None: if self.trait_type is None: obj = new.obj elif isinstance(new.obj, self.trait_type): obj = new.obj self.obj = obj class LabelAction(BoundWorkbenchAction): """ The LabelAction class is the base class for all actions that operate on a Label. """ #### 'BoundWorkbenchAction' interface ##################################### # The type of the object that we will be enabled for. trait_type = Label #### 'BoundAction' interface ############################################## # The bound object. obj = Instance(Label) class LabelIncrementSizeAction(LabelAction): """ The LabelIncrementSizeAction class is a action that increases the size of a label's text. """ #### 'Action' interface ################################################### # The name of the action. name = Unicode("&Increment size") ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): self.obj.increment_size(1) class LabelDecrementSizeAction(LabelAction): """ The LabelDecrementSizeAction class is a action that decreases the size of a label's text. """ #### 'Action' interface ################################################### # The name of the action. name = Unicode("&Decrement size") ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): self.obj.decrement_size(1) class LabelNormalFontAction(LabelAction): """ The LabelNormalFontAction class is a action that sets a normal font for a label's text. """ #### 'Action' interface ################################################### # The name of the action. name = Unicode("&Normal font") ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): self.obj.style = 'normal' class LabelBoldFontAction(LabelAction): """ The LabelNormalFontAction class is a action that sets a bold font for a label's text. """ #### 'Action' interface ################################################### # The name of the action. name = Unicode("&Bold font") ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): self.obj.style = 'bold' class LabelItalicFontAction(LabelAction): """ The LabelNormalFontAction class is a action that sets an italic font for a label's text. """ #### 'Action' interface ################################################### # The name of the action. name = Unicode("&Italic font") ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): self.obj.style = 'italic' apptools-4.1.0/examples/appscripting/example_editor_manager.py0000644000175100001440000001030511674464005025732 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.etsconfig.api import ETSConfig from pyface.workbench.api import Editor, EditorManager class _wxLabelEditor(Editor): """ _wxLabelEditor is the wx implementation of a label editor. """ def create_control(self, parent): import wx w = wx.TextCtrl(parent,style=wx.TE_RICH2) style = w.GetDefaultStyle() style.SetAlignment(wx.TEXT_ALIGNMENT_CENTER) w.SetDefaultStyle(style) self._set_text(w) self._set_size_and_style(w) self.obj.on_trait_change(self._update_text, 'text') self.obj.on_trait_change(self._update_size, 'size') self.obj.on_trait_change(self._update_style, 'style') return w def _name_default(self): return self.obj.text def _update_text(self): self._set_text(self.control) def _set_text(self, w): w.SetValue("") w.WriteText("%s(%d points, %s)" % (self.obj.text, self.obj.size, self.obj.style)) def _update_size(self): self._set_size_and_style(self.control) def _update_style(self): self._set_size_and_style(self.control) def _set_size_and_style(self, w): import wx if self.obj.style == 'normal': style, weight = wx.NORMAL, wx.NORMAL elif self.obj.style == 'italic': style, weight = wx.ITALIC, wx.NORMAL elif self.obj.style == 'bold': style, weight = wx.NORMAL, wx.BOLD else: raise NotImplementedError, 'style "%s" not supported' % self.obj.style f = wx.Font(self.obj.size, wx.ROMAN, style, weight, False) style = wx.TextAttr("BLACK",wx.NullColour,f) w.SetDefaultStyle(style) self._set_text(w) class _PyQt4LabelEditor(Editor): """ _PyQt4LabelEditor is the PyQt implementation of a label editor. """ def create_control(self, parent): from PyQt4 import QtCore, QtGui w = QtGui.QLabel(parent) w.setAlignment(QtCore.Qt.AlignCenter) self._set_text(w) self._set_size(w) self._set_style(w) self.obj.on_trait_change(self._update_text, 'text') self.obj.on_trait_change(self._update_size, 'size') self.obj.on_trait_change(self._update_style, 'style') return w def _name_default(self): return self.obj.text def _update_text(self): self._set_text(self.control) def _set_text(self, w): w.setText("%s\n(%d points, %s)" % (self.obj.text, self.obj.size, self.obj.style)) def _update_size(self): self._set_size(self.control) def _set_size(self, w): f = w.font() f.setPointSize(self.obj.size) w.setFont(f) self._set_text(w) def _update_style(self): self._set_style(self.control) def _set_style(self, w): f = w.font() f.setBold(self.obj.style == 'bold') f.setItalic(self.obj.style == 'italic') w.setFont(f) self._set_text(w) class ExampleEditorManager(EditorManager): """ The ExampleEditorManager class creates the example editors. """ def create_editor(self, window, obj, kind): # Create the toolkit specific editor. tk_name = ETSConfig.toolkit if tk_name == 'wx': ed = _wxLabelEditor(window=window, obj=obj) elif tk_name == 'qt4': ed = _PyQt4LabelEditor(window=window, obj=obj) else: raise NotImplementedError, "unsupported toolkit: %s" % tk_name return ed #### EOF ###################################################################### apptools-4.1.0/examples/appscripting/example_script_window.py0000644000175100001440000001010611674464005025644 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from pyface.action.api import Action, Group, MenuManager from pyface.workbench.api import WorkbenchWindow from pyface.workbench.action.api import MenuBarManager, \ ToolBarManager from traits.api import Instance, on_trait_change from apptools.appscripting.api import get_script_manager from apptools.appscripting.action.api import StartRecordingAction, \ StopRecordingAction # Local imports. from example_editor_manager import ExampleEditorManager from actions import LabelIncrementSizeAction, LabelDecrementSizeAction, \ LabelNormalFontAction, LabelBoldFontAction, LabelItalicFontAction class ExampleScriptWindow(WorkbenchWindow): """ The ExampleScriptWindow class is a workbench window that contains example editors that demonstrate the use of the application scripting framework. """ #### Private interface #################################################### # The action that exits the application. _exit_action = Instance(Action) # The File menu. _file_menu = Instance(MenuManager) # The Label menu. _label_menu = Instance(MenuManager) # The Scripts menu. _scripts_menu = Instance(MenuManager) ########################################################################### # Private interface. ########################################################################### #### Trait initialisers ################################################### def __file_menu_default(self): """ Trait initialiser. """ return MenuManager(self._exit_action, name="&File") def __label_menu_default(self): """ Trait initialiser. """ size_group = Group(LabelIncrementSizeAction(window=self), LabelDecrementSizeAction(window=self)) normal = LabelNormalFontAction(window=self, id='normal', style='radio', checked=True) bold = LabelBoldFontAction(window=self, id='bold', style='radio') italic = LabelItalicFontAction(window=self, id='italic', style='radio') style_group = Group(normal, bold, italic, id='style') return MenuManager(size_group, style_group, name="&Label") def __scripts_menu_default(self): """ Trait initialiser. """ # ZZZ: This is temporary until we put the script into a view. get_script_manager().on_trait_event(self._on_script_updated, 'script_updated') return MenuManager(StartRecordingAction(), StopRecordingAction(), name="&Scripts") def __exit_action_default(self): """ Trait initialiser. """ return Action(name="E&xit", on_perform=self.workbench.exit) def _editor_manager_default(self): """ Trait initialiser. """ return ExampleEditorManager() def _menu_bar_manager_default(self): """ Trait initialiser. """ return MenuBarManager(self._file_menu, self._label_menu, self._scripts_menu, window=self) def _tool_bar_manager_default(self): """ Trait initialiser. """ return ToolBarManager(self._exit_action, show_tool_names=False) # ZZZ: This is temporary until we put the script into a view. def _on_script_updated(self, script_manager): script = script_manager.script if script: print script, else: print "Script empty" #### EOF ###################################################################### apptools-4.1.0/examples/appscripting/example.py0000644000175100001440000000517511674464005022703 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Standard library imports. import logging # Enthought library imports. from pyface.api import GUI, YES from pyface.workbench.api import Workbench # Local imports. from example_script_window import ExampleScriptWindow from model import Label # Log to stderr. logging.getLogger().addHandler(logging.StreamHandler()) logging.getLogger().setLevel(logging.DEBUG) class ExampleScript(Workbench): """ The ExampleScript class is a workbench that creates ExampleScriptWindow windows. """ #### 'Workbench' interface ################################################ # The factory (in this case simply a class) that is used to create # workbench windows. window_factory = ExampleScriptWindow ########################################################################### # Private interface. ########################################################################### def _exiting_changed(self, event): """ Called when the workbench is exiting. """ if self.active_window.confirm('Ok to exit?') != YES: event.veto = True return def main(argv): """ A simple example of using the the application scripting framework in a workbench. """ # Create the GUI. gui = GUI() # Create the workbench. workbench = ExampleScript(state_location=gui.state_location) window = workbench.create_window(position=(300, 300), size=(400, 300)) window.open() # Create some objects to edit. # FIXME v3: The need to explicitly set the style to its default value is # due to a bug in the implementation of Scriptable. label = Label(text="Label", style='normal') label2 = Label(text="Label2", style='normal') # Edit the objects. window.edit(label) window.edit(label2) # Start the GUI event loop. gui.start_event_loop() return if __name__ == '__main__': import sys; main(sys.argv) #### EOF ###################################################################### apptools-4.1.0/examples/naming/0000755000175100001440000000000011674464005017434 5ustar ischnellusers00000000000000apptools-4.1.0/examples/naming/images/0000755000175100001440000000000011674464005020701 5ustar ischnellusers00000000000000apptools-4.1.0/examples/naming/images/open_folder.png0000644000175100001440000000115111674464005023701 0ustar ischnellusers00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<ûIDATxÚbd```ê.øË€J»>h©Û@ü›  æÿ?ÞíÅÀ q ¼s1 31#º~€I0üþr ÃàÜŠµ@2x/!WØ€ÿÿ¾C¹ÿ@>Ó¿>†‚ ‚¯ q2N1HÓ€ðï÷; h(#+XÙ·[¯{ŒËË`W]¹ý»háú¯3jÀ¸‚¿?€5gÏe`bÀv“;‚û€Ô5€‚ðç\âÛ‡« p¹ôÚ/°wþÿyùÃ÷Wë8D¼ÍÑ Hüýæýù~ŸaÕŽï ©)É@C?‚$þÿÿ¡ÿýaøýû/Ûï ó—ìè –ÇõÄoÞ~†%H3Ã_° ¶ÿ…4ÃÏßÿ¸þ~…Ùþˆ<?ù°ãð†Ä7ˆ—þƒbä?Ü ö? µ`ùQ˜íÇø@ xõæ3î£? Þù V áüzáÔ+~þúlûa¯lH³ƒˆ¤!PíHÀýÿwÌ :¨í tÀ Ò @ ¤©ŒÛ« D ý@ªˆŸL  ÔcÄ &D@?Èy€ø(8^ €`™D³C Ã@¹öL3ãÿÿÿ(0ŒþëøIEND®B`‚apptools-4.1.0/examples/naming/images/document.png0000644000175100001440000000051511674464005023226 0ustar ischnellusers00000000000000‰PNG  IHDRóÿabKGDÿÿÿ ½§“ pHYs  šœtIMEÕ8º`&BÚIDAT8Ë­’=n„0…¿H»[!!ŸŸ³l™"ÇAæ!QÚsú­LŠˆ$^X¢¼j,{Þ|3µmû1 à ÇôZ×õ;ÖÚ騬µÓì”ÌÁ8Žxït/5Ç’ˆã˜4MW(wï=ιUÂR’¨ªêW/ÉòPÅ&AHÉòá’à§ž"ÈóBbh H(ýüÿ÷j;Ä`À®€€B‰FpXüÿq R@ ú6äïßß( ÄéŸ]qDˆ qú?°Ìß Pï€Á€%K-`¼’°¹ H­ ¬@lÄÎ@,D¤þï@|ˆ÷,c€hv¨aÄPî΀bü 4r@€œô2s˜^IEND®B`‚apptools-4.1.0/examples/naming/images/image_LICENSE.txt0000644000175100001440000000067111674464005023672 0ustar ischnellusers00000000000000The icons are mostly derived work from other icons. As such they are licensed accordingly to the original license: GV: Gael Varoquaux: BSD-like Unless stated in this file, icons are work of enthought, and are released under BSD-like license. Files and orginal authors: ---------------------------------------------------------------- closed_folder.png | GV document.png | GV open_folder.png | GV apptools-4.1.0/examples/naming/data/0000755000175100001440000000000011674464005020345 5ustar ischnellusers00000000000000apptools-4.1.0/examples/naming/data/foo.txt0000644000175100001440000000001511674464005021665 0ustar ischnellusers00000000000000Hello World! apptools-4.1.0/examples/naming/simple.py0000644000175100001440000000251211674464005021277 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A simple naming example. """ # Standard library imports. import os, sys # Enthought library imports. from apptools.naming.api import Context, InitialContext # Application entry point. if __name__ == '__main__': # Set up the naming environment. klass_name = "apptools.naming.InitialContextFactory" klass_name = "apptools.naming.PyFSInitialContextFactory" environment = {Context.INITIAL_CONTEXT_FACTORY : klass_name} # Create an initial context. context = InitialContext(environment) context.path = os.getcwd() print 'Context', context, context.path print 'Names', context.list_names('') ##### EOF ##################################################################### apptools-4.1.0/examples/naming/explorer.py0000644000175100001440000000605011674464005021647 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A naming system explorer. """ # Standard library imports. import os, sys # FIXME: The below won't work in an egg-based distribution. The real question # is why it was here in the first place. ## Put the Enthought library on the Python path. #sys.path.append(os.path.abspath(r'..\..\..')) # Enthought library imports. from apptools.naming.api import Binding, Context, ContextAdapterFactory from apptools.naming.api import PyFSContext from apptools.naming.adapter import * from apptools.naming.ui.explorer import Explorer from pyface.api import GUI from traits.api import TraitDict, TraitList from apptools.type_manager import TypeManager from traits.util.resource import find_resource # Application entry point. if __name__ == '__main__': # Create the GUI (this does NOT start the GUI event loop). gui = GUI() # Create a type manager to manage context adapters. type_manager = TypeManager() # Add some interesting context adapters. # # Trait dictionaries. type_manager.register_type_adapters( ContextAdapterFactory( adaptee_class=TraitDict, adapter_class=TraitDictContextAdapter, ), TraitDict ) # Trait lists. type_manager.register_type_adapters( ContextAdapterFactory( adaptee_class=TraitList, adapter_class=TraitListContextAdapter, ), TraitList ) # Python dictionaries. type_manager.register_type_adapters( ContextAdapterFactory( adaptee_class=dict, adapter_class=DictContextAdapter, ), dict ) # Python lists. type_manager.register_type_adapters( ContextAdapterFactory( adaptee_class=list, adapter_class=ListContextAdapter, ), list ) # Python objects. type_manager.register_type_adapters( InstanceContextAdapterFactory(), object ) # Get the path to the data directory data_path = os.path.join('examples','naming','data') full_path = find_resource('AppTools', data_path, alt_path='data', return_path=True) # Create the root context. root = PyFSContext(path=full_path) root.environment[Context.TYPE_MANAGER] = type_manager # Create and open the main window. window = Explorer(root=Binding(name='Root', obj=root)) window.open() # Start the GUI event loop. gui.start_event_loop() ##### EOF ##################################################################### apptools-4.1.0/examples/undo/0000755000175100001440000000000011674464005017130 5ustar ischnellusers00000000000000apptools-4.1.0/examples/undo/model.py0000644000175100001440000000266711674464005020615 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import Enum, HasTraits, Int, Unicode class Label(HasTraits): """The Label class implements the data model for a label.""" #### 'Label' interface #################################################### # The name. name = Unicode # The size in points. size = Int(18) # The style. style = Enum('normal', 'bold', 'italic') ########################################################################### # 'Label' interface. ########################################################################### def increment_size(self, by): """Increment the current font size.""" self.size += by def decrement_size(self, by): """Decrement the current font size.""" self.size -= by apptools-4.1.0/examples/undo/commands.py0000644000175100001440000001353511674464005021312 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import Instance, Int, Unicode from apptools.undo.api import AbstractCommand # Local imports. from model import Label class LabelIncrementSizeCommand(AbstractCommand): """ The LabelIncrementSizeCommand class is a command that increases the size of a label's text. This command will merge multiple increments togther. """ #### 'ICommand' interface ################################################# # The data being operated on. data = Instance(Label) # The name of the command. name = Unicode("&Increment size") #### Private interface #################################################### _incremented_by = Int ########################################################################### # 'ICommand' interface. ########################################################################### def do(self): self.data.increment_size(1) self._incremented_by = 1 def merge(self, other): # We can merge if the other command is the same type (or a sub-type). if isinstance(other, type(self)): self._incremented_by += 1 merged = True else: merged = False return merged def redo(self): self.data.increment_size(self._incremented_by) def undo(self): self.data.decrement_size(self._incremented_by) class LabelDecrementSizeCommand(AbstractCommand): """ The LabelDecrementSizeCommand class is a command that decreases the size of a label's text. This command will merge multiple decrements togther. """ #### 'ICommand' interface ################################################# # The data being operated on. data = Instance(Label) # The name of the command. name = Unicode("&Decrement size") #### Private interface #################################################### _decremented_by = Int ########################################################################### # 'ICommand' interface. ########################################################################### def do(self): self.data.decrement_size(1) self._decremented_by = 1 def merge(self, other): # We can merge if the other command is the same type (or a sub-type). if isinstance(other, type(self)): self._decremented_by += 1 merged = True else: merged = False return merged def redo(self): self.data.decrement_size(self._decremented_by) def undo(self): self.data.increment_size(self._decremented_by) class LabelNormalFontCommand(AbstractCommand): """ The LabelNormalFontCommand class is a command that sets a normal font for a label's text. """ #### 'ICommand' interface ################################################# # The data being operated on. data = Instance(Label) # The name of the command. name = Unicode("&Normal font") ########################################################################### # 'ICommand' interface. ########################################################################### def do(self): # Save the old value. self._saved = self.data.style # Calling redo() is a convenient way to update the model now that the # old value is saved. self.redo() def redo(self): self.data.style = 'normal' def undo(self): self.data.style = self._saved class LabelBoldFontCommand(AbstractCommand): """ The LabelNormalFontCommand class is a command that sets a bold font for a label's text. """ #### 'ICommand' interface ############################################# # The data being operated on. data = Instance(Label) # The name of the command. name = Unicode("&Bold font") ########################################################################### # 'ICommand' interface. ########################################################################### def do(self): # Save the old value. self._saved = self.data.style # Calling redo() is a convenient way to update the model now that the # old value is saved. self.redo() def redo(self): self.data.style = 'bold' def undo(self): self.data.style = self._saved class LabelItalicFontCommand(AbstractCommand): """ The LabelNormalFontCommand class is a command that sets an italic font for a label's text. """ #### 'ICommand' interface ################################################# # The data being operated on. data = Instance(Label) # The name of the command. name = Unicode("&Italic font") ########################################################################### # 'ICommand' interface. ########################################################################### def do(self): # Save the old value. self._saved = self.data.style # Calling redo() is a convenient way to update the model now that the # old value is saved. self.redo() def redo(self): self.data.style = 'italic' def undo(self): self.data.style = self._saved apptools-4.1.0/examples/undo/example_editor_manager.py0000644000175100001440000001026411674464005024200 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.etsconfig.api import ETSConfig from pyface.workbench.api import Editor, EditorManager class _wxLabelEditor(Editor): """ _wxLabelEditor is the wx implementation of a label editor. """ def create_control(self, parent): import wx w = wx.TextCtrl(parent,style=wx.TE_RICH2) style = w.GetDefaultStyle() style.SetAlignment(wx.TEXT_ALIGNMENT_CENTER) w.SetDefaultStyle(style) self._set_text(w) self._set_size_and_style(w) self.obj.on_trait_change(self._update_text, 'text') self.obj.on_trait_change(self._update_size, 'size') self.obj.on_trait_change(self._update_style, 'style') return w def _name_default(self): return self.obj.text def _update_text(self): self._set_text(self.control) def _set_text(self, w): w.SetValue("") w.WriteText("%s(%d points, %s)" % (self.obj.text, self.obj.size, self.obj.style)) def _update_size(self): self._set_size_and_style(self.control) def _update_style(self): self._set_size_and_style(self.control) def _set_size_and_style(self, w): import wx if self.obj.style == 'normal': style, weight = wx.NORMAL, wx.NORMAL elif self.obj.style == 'italic': style, weight = wx.ITALIC, wx.NORMAL elif self.obj.style == 'bold': style, weight = wx.NORMAL, wx.BOLD else: raise NotImplementedError, 'style "%s" not supported' % self.obj.style f = wx.Font(self.obj.size, wx.ROMAN, style, weight, False) style = wx.TextAttr("BLACK",wx.NullColour,f) w.SetDefaultStyle(style) self._set_text(w) class _PyQt4LabelEditor(Editor): """ _PyQt4LabelEditor is the PyQt implementation of a label editor. """ def create_control(self, parent): from PyQt4 import QtCore, QtGui w = QtGui.QLabel(parent) w.setAlignment(QtCore.Qt.AlignCenter) self._set_text(w) self._set_size(w) self._set_style(w) self.obj.on_trait_change(self._update_text, 'text') self.obj.on_trait_change(self._update_size, 'size') self.obj.on_trait_change(self._update_style, 'style') return w def _name_default(self): return self.obj.text def _update_text(self): self._set_text(self.control) def _set_text(self, w): w.setText("%s\n(%d points, %s)" % (self.obj.text, self.obj.size, self.obj.style)) def _update_size(self): self._set_size(self.control) def _set_size(self, w): f = w.font() f.setPointSize(self.obj.size) w.setFont(f) self._set_text(w) def _update_style(self): self._set_style(self.control) def _set_style(self, w): f = w.font() f.setBold(self.obj.style == 'bold') f.setItalic(self.obj.style == 'italic') w.setFont(f) self._set_text(w) class ExampleEditorManager(EditorManager): """ The ExampleEditorManager class creates the example editors. """ def create_editor(self, window, obj, kind): # Create the toolkit specific editor. tk_name = ETSConfig.toolkit if tk_name == 'wx': ed = _wxLabelEditor(window=window, obj=obj) elif tk_name == 'qt4': ed = _PyQt4LabelEditor(window=window, obj=obj) else: raise NotImplementedError, "unsupported toolkit: %s" % tk_name return ed #### EOF ###################################################################### apptools-4.1.0/examples/undo/example_undo_window.py0000644000175100001440000001215511674464005023555 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from pyface.action.api import Action, Group, MenuManager from pyface.workbench.api import WorkbenchWindow from pyface.workbench.action.api import MenuBarManager, \ ToolBarManager from traits.api import Instance, on_trait_change from apptools.undo.action.api import CommandAction, RedoAction, UndoAction # Local imports. from example_editor_manager import ExampleEditorManager from commands import LabelIncrementSizeCommand, LabelDecrementSizeCommand, \ LabelNormalFontCommand, LabelBoldFontCommand, LabelItalicFontCommand class ExampleUndoWindow(WorkbenchWindow): """ The ExampleUndoWindow class is a workbench window that contains example editors that demonstrate the use of the undo framework. """ #### Private interface #################################################### # The action that exits the application. _exit_action = Instance(Action) # The File menu. _file_menu = Instance(MenuManager) # The Label menu. _label_menu = Instance(MenuManager) # The Undo menu. _undo_menu = Instance(MenuManager) ########################################################################### # Private interface. ########################################################################### #### Trait initialisers ################################################### def __file_menu_default(self): """ Trait initialiser. """ return MenuManager(self._exit_action, name="&File") def __undo_menu_default(self): """ Trait initialiser. """ undo_manager = self.workbench.undo_manager undo_action = UndoAction(undo_manager=undo_manager) redo_action = RedoAction(undo_manager=undo_manager) return MenuManager(undo_action, redo_action, name="&Undo") def __label_menu_default(self): """ Trait initialiser. """ size_group = Group(CommandAction(command=LabelIncrementSizeCommand), CommandAction(command=LabelDecrementSizeCommand)) normal = CommandAction(id='normal', command=LabelNormalFontCommand, style='radio', checked=True) bold = CommandAction(id='bold', command=LabelBoldFontCommand, style='radio') italic = CommandAction(id='italic', command=LabelItalicFontCommand, style='radio') style_group = Group(normal, bold, italic, id='style') return MenuManager(size_group, style_group, name="&Label") def __exit_action_default(self): """ Trait initialiser. """ return Action(name="E&xit", on_perform=self.workbench.exit) def _editor_manager_default(self): """ Trait initialiser. """ return ExampleEditorManager() def _menu_bar_manager_default(self): """ Trait initialiser. """ return MenuBarManager(self._file_menu, self._label_menu, self._undo_menu, window=self) def _tool_bar_manager_default(self): """ Trait initialiser. """ return ToolBarManager(self._exit_action, show_tool_names=False) def _active_editor_changed(self, old, new): """ Trait handler. """ # Tell the undo manager about the new command stack. if old is not None: old.command_stack.undo_manager.active_stack = None if new is not None: new.command_stack.undo_manager.active_stack = new.command_stack # Walk the label editor menu. for grp in self._label_menu.groups: for itm in grp.items: action = itm.action # Enable the action and set the command stack and data if there # is a new editor. if new is not None: action.enabled = True action.command_stack = new.command_stack action.data = new.obj # FIXME v3: We should just be able to check the menu option # corresponding to the style trait - but that doesn't seem # to uncheck the other options in the group. Even then the # first switch to another editor doesn't update the menus # (though subsequent ones do). if grp.id == 'style': action.checked = (action.data.style == action.id) else: action.enabled = False #### EOF ###################################################################### apptools-4.1.0/examples/undo/example.py0000644000175100001440000000464011674464005021141 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Standard library imports. import logging # Enthought library imports. from pyface.api import GUI, YES from pyface.workbench.api import Workbench # Local imports. from example_undo_window import ExampleUndoWindow from model import Label # Log to stderr. logging.getLogger().addHandler(logging.StreamHandler()) logging.getLogger().setLevel(logging.DEBUG) class ExampleUndo(Workbench): """ The ExampleUndo class is a workbench that creates ExampleUndoWindow windows. """ #### 'Workbench' interface ################################################ # The factory (in this case simply a class) that is used to create # workbench windows. window_factory = ExampleUndoWindow ########################################################################### # Private interface. ########################################################################### def _exiting_changed(self, event): """ Called when the workbench is exiting. """ if self.active_window.confirm('Ok to exit?') != YES: event.veto = True return def main(argv): """ A simple example of using the the undo framework in a workbench. """ # Create the GUI. gui = GUI() # Create the workbench. workbench = ExampleUndo(state_location=gui.state_location) window = workbench.create_window(position=(300, 300), size=(400, 300)) window.open() # Create some objects to edit. label = Label(text="Label") label2 = Label(text="Label2") # Edit the objects. window.edit(label) window.edit(label2) # Start the GUI event loop. gui.start_event_loop() return if __name__ == '__main__': import sys; main(sys.argv) #### EOF ###################################################################### apptools-4.1.0/image_LICENSE.txt0000644000175100001440000000165711674464005017343 0ustar ischnellusers00000000000000The icons are mostly derived work from other icons. As such they are licensed accordingly to the original license: Project License File ---------------------------------------------------------------------------- GV: Gael Varoquaux BSD-like LICENSE.txt Enthought BSD-like LICENSE.txt Nuvola LGPL image_LICENSE_Nuvola.txt OOo LGPL image_LICENSE_OOo.txt Unless stated in this file, icons are work of Enthought, and are released under BSD-like license. Files and original authors: ---------------------------------------------------------------- apptools/help: help_action.png | GV apptools/naming/ui/images (and examples/naming/images): closed_folder.png | GV document.png | GV open_folder.png | GV apptools-4.1.0/docs/0000755000175100001440000000000011674464005015275 5ustar ischnellusers00000000000000apptools-4.1.0/docs/source/0000755000175100001440000000000011674464005016575 5ustar ischnellusers00000000000000apptools-4.1.0/docs/source/_static/0000755000175100001440000000000011674464005020223 5ustar ischnellusers00000000000000apptools-4.1.0/docs/source/_static/e-logo-rev.png0000644000175100001440000000751111674464005022711 0ustar ischnellusers00000000000000‰PNG  IHDRoi»þê*sRGB®Îé pHYs  šœtIMEØ“ïLÛIDATxÚí]ktTÕÙ>ûzÎ\$Üm ¬€ ‰È¥!TDŠˆ j‘V|_CèWp­Š¡hµ ¢PµTº´•›\"ÍÍBÉ$$”6a’IfÎ9ûûqfB¸%™™=“ ýƒµ²Bæìyæ}ÞÛÞÏ; vØo„Ðâ´`‚š!4Ch†VÍv[¸ƒì“1¦2¦ Œ€!ÛôH¥G”iÝêygò×þöÅ):QVd»ª8Sƒm«¨sQAŒ£j2àô§’ß~ýÙ~ÑÝAH3ûÑqÕÕUÅÿ:€÷?!4[a6SRö—u ÇŽÔü7¢H'Ž1"¡_Qqéåÿâ!šŒ©JbBôæwÍx$IÉ-ÿSï^QO>þAòO~ߨ( ‚ ÚÛHAPU–Œ)=»…­úý“£û·ñOjjm+þðþöÇ– ¤ v$~°Ø¦æ"3¦¼ñê3½zviûJnâ[­*.\vÛ(ü/E“1U`ÊÜYcÖ¿¾ í&y3ñg¦>Ð=Êx$÷;G£S@»¿]ÑdŒ1åÁ±ñYûüÓoç"Û¾Å÷ýX²½¾6ÿÛÓn8ñÛÍo2¦ ˆí¶,cú¨ÄÜ_¼¸äÜŠU?y" "ºcÑdL5HfFjÚÔÑ~}ÐÖÏö½œýQ½]€HÀ™”錩ŒÉ/¤OxcÕü¡ƒûùûq'~ lÓ•_´ Å£Íe•W\ÌÈ|»ñ¡ €Š&cLM½hÁd¸È¶¯î9¶òÕ-Õ—ê $þ ¾™Î˜Ú³›9ó¥iË—>x“¼aÅôë•6í>‚Ô#¹E‚ ¹ß_h2¦šŒ$}î¸ì•¿àâ"çä®ßða\lß°0³×/"‰ôÞ‘ƒÒ¦Ž.*:UQyÙ•éÀ‹ø~`:oYf­xuõú?ù b)<Ìœ>ÆÒ—æ'ñù¢ér‘Y‹gÄèãûËÕÔÔ¾µá¯6n««g˜šÑ€˜êìeøSö‹£ïMðõõkm›>ØñÞæýuv" òSnhj ‹ÌŒé’‡ryÁ_îÍüÝšÊêLM˜š01@$™ª(²]vÚÆüòòçúôîî{Ä_ñÊ{#¬ƒˆ¼&>4µl|îcýì.8~ÿÛåÙÇrK05bjÂÔˆ°Þm;€1•©²¢4*Î:½¤ÎŸ3yÉKó|è‘ÜÂÅËÞ©ºT\©> 8šn™µd¦Ù¤çBíÿ]¶jë§»]8#&ú[ù5ÆTEUŠl—uÝ#u\ˆ/Âkþø½¿í¯kð’ø^Çô6õt=Zo­ÿ`ÎÓ‹¿-,£R'*EP1#¼EQ€ ˆDÔfsnýlOYYYÒ¨!’$ú²‡{Gš2y„ÕZ~æ\p·õÛN|olÓ‹žn«ÙϯýîBu-Ñ\$5¶½õ˘ÂTY‘gNR^\8óÙgåFü‹õ{@|Ñ4Å…¿œ0ov2­ÖŠç^È:j)ÆÔD¨"z©‡M øEn¶þ1]Wf¥Nâ@üwßÿǛ뿨k ÛB|™ÞÐÐð«ùcÂÃÂ!>ºÈ×ßÜ8çéÿ©ºT'J©Ô‰ˆaZà†xØèubˆèOWê¶~º»¬Ìê;ñ‡ÝÓöcÉùù%ª®ÀÖÐdLõ<½bj™µ|ïׇΜýÁë]nÞ²}ðð”5olu]%}Õu!b'ìÜ^f'BDÑ1Œêºüý˼÷Í-,>í£ynØ¸í»¢ïÝÇ÷¬ed°çNSÁét~WX\f-–pO¸'¥ÞáœÜÌå,9UŽ©I2˜1`¬ÓxÄ¥d  ,Œ»úåù½{Eúäͽ¢êR•"¨¤Z GÌ 4›¯ÿÔÔîûúPL¿èøq­¿©@ÄÔ$é#15! G®}\.-ý2kEfVö®=G15Q©¡&­phõáî!9ûCeÕÅá CºvéÜj(ê# 5#¢GXò:In¡ˆÈÌHó±¥ÿJöZm«®œÚžÏó¹ÕU__(çhî݆' ¹ÁH›ˆ¢Á„‰ûÁ7cªÉHçκÞì})"´D­òb-¦&Ñ`ÆÄài­ÉóŽ\eUõ®Ý?ň‹éí*³²YJ05ŠúÈë DÈË ¹ô«šS[ÔGabDÄ›­r¾q¨E§ÊªêÃ9–Õ¯m"4LÔwÅÔt›ÑG‹Túúدr{¡OêìÞPÛ¿hjëò?:}•"ˆްžïi ¯bì:/¤7yAí@ ©eKˆÖ#$òt‘’™13mj’ï5Ø1K ¦&Ž^Èw‹€ !æ%¯PSSSûÖ†³×lr{sž^¨#ÜÔæw4ÒDmQI¨ S#ß3aì@ò57Q; S#Â:į v4y…¿R» É+Ô€ÚA&¯P0j/šŒ)©)Ã-ø¹¡&ÔZ4ÙS³Æ.[<£cQ;XÑdÂ{›÷^¾ô±ñ}; µoXA£e‚`n^éÄÔ¥ËW®«©¹êµ_É^7$ñKþ9Q)ê#E]¡&„u€SíÐÑÐa‰Póû[Ž;ï£m;ÛHíû’[óÆfQ)é£D}W"†#b€ˆ^™D:K „;5:õK²6¦>žÑÂÁŽÕZ‘2õ©¹Ïd^üÑ!j8J01ºØ-´ƒj(ÈT«ˆ!–05Q)"¯ jâÔŒå+ÖÞ@üà¡vp£Ùd¤ˆ"¢'4LÔuyÿ£C͉¿cç¾à¡v‡©,@A1„´Ñi_’µqÛöýLq|s4b©½¢vGîz†ˆ!¢ùUªâ Ý!¢KIÈ裸‹øQD”1E3[÷¹1ª­v”I $0¦ýœ›ì0h6øFŒûÂLëÒâÐÌ™–€dªœš’°nõuËž?SScw\L÷G¨aʃcf-Nó]yç/4u:‘© c c P°¥ÙM1›¯8Ù_h>4~´ÁhÞ½¿Äf· ‚l% &N^˜>‰ïü?2=iÔÝC‡ÄÈ)Î9vÞ!Ëœïújæ>‘ìû±h ý¦N'N~(aݘ¯öä}WÕΠx+ïÚ' uŽ0?1sLâÐ _í/>_nƒH Ø!â ÙxöÊ9½zøqè@àbzlLÏØ˜ž¹ÇOíþú_µ6ôAêc6~çt=F&ö™Øמüœcç2ò\¶æ±‹ärÇh‚3ç.Æôòå©“Jx`ÌÀ¿ÿãx^_œ©–¿>‘_¨mqKžk€™jÉ+-++ÿYŸ.z½äõîÁwì3°TUõå+ÿ¶q±áܹ~Mú„ä¡\ÄÉ_î:pð›BÍI¶äš€" Dôß5 ûæ«Jc¯ž‘„øTÅêubdãÐ!ƒ¸PûÅ%+³~ÿfõ¥z*EP]½6Rû2;Ú/uzÓ] ÂâÞg,'ÎNdÔ=Câ„ö^n1¿ê8ö´ü8‚D,ïd©ÕZ}W”/Ñ)~€÷ŸÇŽ/÷¦Íz~÷¾ã*0P]g*…Ý[{ÐÏs‹]ÄÇb„è¥ëŽæ76ÖÇÅö $š…ß?½`éÚõ[í˜JTìDD“ËKÂŽ¦ t ‰"Û÷(µœ(}òñqq±wùûÑZøÎ¦O11ІÈfƒéüÒZ XSXk71E›½~í;{âûGþbö½NòÓ#Ý"kæ"ý« è„r@×4½Ë—m‡rN"¨öîÁ—é‡sr§L›ÿùΌڀ?íVh¿?ÝÝnõ²ãêÄñw'?0lÛ¶¯Žä`bÀÔìõ´·ÿF4]2UUœªÒ ;lŠlgL bðkx'øÍÛ„§&µ%QU#c*€Bð{awš×E|ÈDíǶ¡Bh¶ìZ±–-„fÍš¡åÙúߣÚ/Ùw¿IEND®B`‚apptools-4.1.0/docs/source/_static/default.css0000644000175100001440000003231311674464005022363 0ustar ischnellusers00000000000000/** * Sphinx Doc Design */ body { font-family: 'Verdana', 'Helvetica', 'Arial', sans-serif; font-size: 100%; background-color: #333333; color: #000; margin: 0; padding: 0; } /* :::: LAYOUT :::: */ div.document { background-color: #24326e; } div.documentwrapper { float: left; width: 100%; } div.bodywrapper { margin: 0 0 0 230px; } div.body { background-color: white; padding: 0 20px 30px 20px; } div.sphinxsidebarwrapper { padding: 10px 5px 0 10px; } div.sphinxsidebar { float: left; width: 230px; margin-left: -100%; font-size: 90%; } p.logo { text-align: center; } div.clearer { clear: both; } div.footer { color: #fff; width: 100%; padding: 9px 0 9px 0; text-align: center; font-size: 75%; } div.footer a { color: #fff; text-decoration: underline; } div.related { background-color: #24326e; color: #fff; width: 100%; height: 30px; line-height: 30px; font-size: 90%; } div.related h3 { display: none; } div.related ul { margin: 0; padding: 0 0 0 10px; list-style: none; } div.related li { display: inline; } div.related li.right { float: right; margin-right: 5px; } div.related a { color: white; } /* ::: TOC :::: */ div.sphinxsidebar h3 { font-family: 'Verdana', 'Helvetica', 'Arial', sans-serif; color: #acafb3; font-size: 1.4em; font-weight: normal; margin: 0; padding: 0; } div.sphinxsidebar h4 { font-family: 'Verdana', 'Helvetica', 'Arial', sans-serif; color: #acafb3; font-size: 1.3em; font-weight: normal; margin: 5px 0 0 0; padding: 0; } div.sphinxsidebar p { color: white; } div.sphinxsidebar p.topless { margin: 5px 10px 10px 10px; } div.sphinxsidebar ul { margin: 10px; padding: 0; list-style: none; color: white; } div.sphinxsidebar ul ul, div.sphinxsidebar ul.want-points { margin-left: 20px; list-style: square; } div.sphinxsidebar ul ul { margin-top: 0; margin-bottom: 0; } div.sphinxsidebar a { color: #fff; } div.sphinxsidebar form { margin-top: 10px; } div.sphinxsidebar input { border: 1px solid #9bbde2; font-family: 'Verdana', 'Helvetica', 'Arial', sans-serif; font-size: 1em; } /* :::: MODULE CLOUD :::: */ div.modulecloud { margin: -5px 10px 5px 10px; padding: 10px; line-height: 160%; border: 1px solid #666666; background-color: #dddddd; } div.modulecloud a { padding: 0 5px 0 5px; } /* :::: SEARCH :::: */ ul.search { margin: 10px 0 0 20px; padding: 0; } ul.search li { padding: 5px 0 5px 20px; background-image: url(file.png); background-repeat: no-repeat; background-position: 0 7px; } ul.search li a { font-weight: bold; } ul.search li div.context { color: #666; margin: 2px 0 0 30px; text-align: left; } ul.keywordmatches li.goodmatch a { font-weight: bold; } /* :::: COMMON FORM STYLES :::: */ div.actions { padding: 5px 10px 5px 10px; border-top: 1px solid #598ec0; border-bottom: 1px solid #598ec0; background-color: #9bbde2; } form dl { color: #333; } form dt { clear: both; float: left; min-width: 110px; margin-right: 10px; padding-top: 2px; } input#homepage { display: none; } div.error { margin: 5px 20px 0 0; padding: 5px; border: 1px solid #db7d46; font-weight: bold; } /* :::: INLINE COMMENTS :::: */ div.inlinecomments { position: absolute; right: 20px; } div.inlinecomments a.bubble { display: block; float: right; background-image: url(style/comment.png); background-repeat: no-repeat; width: 25px; height: 25px; text-align: center; padding-top: 3px; font-size: 0.9em; line-height: 14px; font-weight: bold; color: black; } div.inlinecomments a.bubble span { display: none; } div.inlinecomments a.emptybubble { background-image: url(style/nocomment.png); } div.inlinecomments a.bubble:hover { background-image: url(style/hovercomment.png); text-decoration: none; color: #598ec0; } div.inlinecomments div.comments { float: right; margin: 25px 5px 0 0; max-width: 50em; min-width: 30em; border: 1px solid #598ec0; background-color: #9bbde2; z-index: 150; } div#comments { border: 1px solid #598ec0; margin-top: 20px; } div#comments div.nocomments { padding: 10px; font-weight: bold; } div.inlinecomments div.comments h3, div#comments h3 { margin: 0; padding: 0; background-color: #598ec0; color: white; border: none; padding: 3px; } div.inlinecomments div.comments div.actions { padding: 4px; margin: 0; border-top: none; } div#comments div.comment { margin: 10px; border: 1px solid #598ec0; } div.inlinecomments div.comment h4, div.commentwindow div.comment h4, div#comments div.comment h4 { margin: 10px 0 0 0; background-color: #2eabb0; color: white; border: none; padding: 1px 4px 1px 4px; } div#comments div.comment h4 { margin: 0; } div#comments div.comment h4 a { color: #9bbde2; } div.inlinecomments div.comment div.text, div.commentwindow div.comment div.text, div#comments div.comment div.text { margin: -5px 0 -5px 0; padding: 0 10px 0 10px; } div.inlinecomments div.comment div.meta, div.commentwindow div.comment div.meta, div#comments div.comment div.meta { text-align: right; padding: 2px 10px 2px 0; font-size: 95%; color: #598ec0; border-top: 1px solid #598ec0; background-color: #9bbde2; } div.commentwindow { position: absolute; width: 500px; border: 1px solid #598ec0; background-color: #9bbde2; display: none; z-index: 130; } div.commentwindow h3 { margin: 0; background-color: #598ec0; color: white; border: none; padding: 5px; font-size: 1.5em; cursor: pointer; } div.commentwindow div.actions { margin: 10px -10px 0 -10px; padding: 4px 10px 4px 10px; color: #598ec0; } div.commentwindow div.actions input { border: 1px solid #598ec0; background-color: white; color: #073d61; cursor: pointer; } div.commentwindow div.form { padding: 0 10px 0 10px; } div.commentwindow div.form input, div.commentwindow div.form textarea { border: 1px solid #598ec0; background-color: white; color: black; } div.commentwindow div.error { margin: 10px 5px 10px 5px; background-color: #fff2b0; display: none; } div.commentwindow div.form textarea { width: 99%; } div.commentwindow div.preview { margin: 10px 0 10px 0; background-color: ##9bbde2; padding: 0 1px 1px 25px; } div.commentwindow div.preview h4 { margin: 0 0 -5px -20px; padding: 4px 0 0 4px; color: white; font-size: 1.3em; } div.commentwindow div.preview div.comment { background-color: #f2fbfd; } div.commentwindow div.preview div.comment h4 { margin: 10px 0 0 0!important; padding: 1px 4px 1px 4px!important; font-size: 1.2em; } /* :::: SUGGEST CHANGES :::: */ div#suggest-changes-box input, div#suggest-changes-box textarea { border: 1px solid #666; background-color: white; color: black; } div#suggest-changes-box textarea { width: 99%; height: 400px; } /* :::: PREVIEW :::: */ div.preview { background-image: url(style/preview.png); padding: 0 20px 20px 20px; margin-bottom: 30px; } /* :::: INDEX PAGE :::: */ table.contentstable { width: 90%; } table.contentstable p.biglink { line-height: 150%; } a.biglink { font-size: 1.3em; } span.linkdescr { font-style: italic; padding-top: 5px; font-size: 90%; } /* :::: INDEX STYLES :::: */ table.indextable td { text-align: left; vertical-align: top; } table.indextable dl, table.indextable dd { margin-top: 0; margin-bottom: 0; } table.indextable tr.pcap { height: 10px; } table.indextable tr.cap { margin-top: 10px; background-color: #dddddd; } img.toggler { margin-right: 3px; margin-top: 3px; cursor: pointer; } form.pfform { margin: 10px 0 20px 0; } /* :::: GLOBAL STYLES :::: */ .docwarning { background-color: #fff2b0; padding: 10px; margin: 0 -20px 0 -20px; border-bottom: 1px solid #db7d46; } p.subhead { font-weight: bold; margin-top: 20px; } a { color: #24326e; text-decoration: none; } a:hover { text-decoration: underline; } div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { font-family: 'Verdana', 'Helvetica', 'Arial', sans-serif; background-color: #dddddd; font-weight: normal; color: #073d61; border-bottom: 1px solid #666; margin: 20px -20px 10px -20px; padding: 3px 0 3px 10px; } div.body h1 { margin-top: 0; font-size: 200%; } div.body h2 { font-size: 160%; } div.body h3 { font-size: 140%; } div.body h4 { font-size: 120%; } div.body h5 { font-size: 110%; } div.body h6 { font-size: 100%; } a.headerlink { color: #edaa1e; font-size: 0.8em; padding: 0 4px 0 4px; text-decoration: none; visibility: hidden; } h1:hover > a.headerlink, h2:hover > a.headerlink, h3:hover > a.headerlink, h4:hover > a.headerlink, h5:hover > a.headerlink, h6:hover > a.headerlink, dt:hover > a.headerlink { visibility: visible; } a.headerlink:hover { background-color: #edaa1e; color: white; } div.body p, div.body dd, div.body li { text-align: left; line-height: 130%; } div.body p.caption { text-align: inherit; } div.body td { text-align: left; } ul.fakelist { list-style: none; margin: 10px 0 10px 20px; padding: 0; } .field-list ul { padding-left: 1em; } .first { margin-top: 0 !important; } /* "Footnotes" heading */ p.rubric { margin-top: 30px; font-weight: bold; } /* "Topics" */ div.topic { background-color: #ddd; border: 1px solid #666; padding: 0 7px 0 7px; margin: 10px 0 10px 0; } p.topic-title { font-size: 1.1em; font-weight: bold; margin-top: 10px; } /* Admonitions */ div.admonition { margin-top: 10px; margin-bottom: 10px; padding: 7px; } div.admonition dt { font-weight: bold; } div.admonition dl { margin-bottom: 0; } div.admonition p { display: inline; } div.seealso { background-color: #fff2b0; border: 1px solid #edaa1e; } div.warning { background-color: #fff2b0; border: 1px solid ##db7d46; } div.note { background-color: #eee; border: 1px solid #666; } p.admonition-title { margin: 0px 10px 5px 0px; font-weight: bold; display: inline; } p.admonition-title:after { content: ":"; } div.body p.centered { text-align: center; margin-top: 25px; } table.docutils { border: 0; } table.docutils td, table.docutils th { padding: 1px 8px 1px 0; border-top: 0; border-left: 0; border-right: 0; border-bottom: 1px solid #a9a6a2; } table.field-list td, table.field-list th { border: 0 !important; } table.footnote td, table.footnote th { border: 0 !important; } .field-list ul { margin: 0; padding-left: 1em; } .field-list p { margin: 0; } dl { margin-bottom: 15px; clear: both; } dd p { margin-top: 0px; } dd ul, dd table { margin-bottom: 10px; } dd { margin-top: 3px; margin-bottom: 10px; margin-left: 30px; } .refcount { color: #24326e; } dt:target, .highlight { background-color: #edaa1e1; } dl.glossary dt { font-weight: bold; font-size: 1.1em; } th { text-align: left; padding-right: 5px; } pre { padding: 5px; background-color: #e6f3ff; color: #333; border: 1px solid #24326e; border-left: none; border-right: none; overflow: auto; } td.linenos pre { padding: 5px 0px; border: 0; background-color: transparent; color: #aaa; } table.highlighttable { margin-left: 0.5em; } table.highlighttable td { padding: 0 0.5em 0 0.5em; } tt { background-color: #ddd; padding: 0 1px 0 1px; font-size: 0.95em; } tt.descname { background-color: transparent; font-weight: bold; font-size: 1.2em; } tt.descclassname { background-color: transparent; } tt.xref, a tt { background-color: transparent; font-weight: bold; } .footnote:target { background-color: #fff2b0 } h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { background-color: transparent; } .optional { font-size: 1.3em; } .versionmodified { font-style: italic; } form.comment { margin: 0; padding: 10px 30px 10px 30px; background-color: #ddd; } form.comment h3 { background-color: #598ec0; color: white; margin: -10px -30px 10px -30px; padding: 5px; font-size: 1.4em; } form.comment input, form.comment textarea { border: 1px solid #ddd; padding: 2px; font-family: 'Verdana', 'Helvetica', 'Arial', sans-serif; font-size: 100%; } form.comment input[type="text"] { width: 240px; } form.comment textarea { width: 100%; height: 200px; margin-bottom: 10px; } .system-message { background-color: #edaa1e; padding: 5px; border: 3px solid red; } /* :::: PRINT :::: */ @media print { div.document, div.documentwrapper, div.bodywrapper { margin: 0; width : 100%; } div.sphinxsidebar, div.related, div.footer, div#comments div.new-comment-box, #top-link { display: none; } } apptools-4.1.0/docs/source/_static/et.ico0000644000175100001440000002362611674464005021340 0ustar ischnellusers00000000000000(f èŽ00hvhÞ ¨F00¨î( r5(H73iKD]ZZzol‘ˆ…ž—•¤£¢µ±¯¿½¼ÇÅÅÕÔÔàßßìììùùùìs$jÞì¤V4{Þí¶H¦5ŒÚF™g@H•™gÎ @'ˆ„îè 9ˆc®ìªÎå]ˆsžÕE{Þàˆc@EŒàˆc{bF­‰QE«Pk„G–\Ø 1¾Ö®Ä}à”màðÀ€Àðü( @_-!p3%7)&[B<]VU~okŠ„‚˜”’§¥¤´°®»¸¶ÄÃÃÓÒÒæåå÷÷÷í¥W½àíÆU#XÍàØWµ$iÍàÛUª¥$zÞàìuŠ™¥5{Þí•j™™µ6Œîí¶Y©™ªv1G¬àØW©™šwÞ×"XÎ fª™š‡ÎÅ$¾š™š—¾à´®©™¦àƒ®™˜Ià q¾™§Jà QΙ—JàîÞàë[î™§JàÚ‰Îàî™—JàÜc4jÞ™§JàÄ5{Þ™—Jà 1Fœî™—Jà qG­à™§IÞ Q#WÍà™§6œî Q$jÎઔ#X½àêA${à¦#hÍàç1IÐQ$jÎîÈ1<à s4{ÉAÎ a5S®à Q}àêA\àç1JîÖ7ÞÅΕ¾àÿðÿÿÀÿÿ€þøðÀ€€À€ð€ø€þ€ÿ€€|À€xó€pÿ€`ÿ€`?€`€8€€€À€€àøüÿÿÿÀÿÿðÿÿøÿ(0`W,"4(%p3%v>1IGF~QFja_~wtމ‡ž›š³¯¬¿¼ºÔÔÔêêêùùùí¹›ÎàíÉdF›ÝàÚwµG¬Þà܆›µ h¼Þ즋ª¥" i¼îí·jºª¥""!z½îíÈiºªªµ"""!ŠÍàÚgºªªª¥""""‹ÎàÛ†«ªªªªµ"""""F›Ýàì–‹ªªªªº†""""""G¬Þàí§zºªªª«™Þ¥""""""H¬ÞÈiºªªª«˜Î “""""" iÎàëhºªªª«¨¾à s""""" œà¸«ªªªª¨­à c"""""!Œà›ªªªª¹à R""""!|à›ªªª¹Œàé2"""!|à›ªªª{îØ2""!|à›ªª”­Ç2"1Œà›ªª„½µ"!Œà›ª«„­£5­›ªª„®îà ˆÞ›ª«„­ìÌÞ›ªª„®í¸y¼î›ª«„­íÈ@yÍª„½Ú`"!ŠÎà›ª«„­“"""‹Íà›ªª„­ 2""""G¬Þà›ª«„½ s"""""H¬Þ›ªª„­ b"""" i½Þ›ª«„­à R""""!yÍª„ŒÞéR""""!ŠÍà›ª«qh¼îØ2""""›Þà›ª§0iÌîÇ2""""GœÞ›©R"!zÍà¶"""""hΛr"""ŠÎàìr"""" Ε"""""F›ÝàíÈB""""#9ÞØ3"""""GœÞíÊB"""""6¾Ç2"""""h¬Ëp"""""%à¶""""" ht"""""#|à¥"""""!"""""kà ƒ"""""""""""YÞ s"""""""""8Î b"""""""&¾êR"""""%àØ2"""#|àÇ2"#kà¶"YÞ©ÞÿÿðÿÿÿÿÀÿÿÿÿ€ÿÿþ?ÿÿøÿÿðÿÿÀÿÿ€þøðà€ÀàÀøÀþÀÿÿÀÿÿÀÀÿÿðÀÿÿüÀÿÿþÀþÿƒÀøÿïÀðÿÿÀÀÿÿÀ€ÿÀ€ÿÀ€ÿÀ€ÿÀàÿÀø?ÀüÀÿÀ?ÀÀðÀðÀÀÀðüþÿÿ€ÿÿàÿÿø?ÿÿüÿÿÿÿÿÿÿÀÿÿÿÿðÿÿÿÿø?ÿÿ( f-t1!j/#i1$n3$j2&r4&s4&s5&u5&]0'`1'^2'u5'v5'v6'x6';+(X2(_2(r4(u6(w7(x7(z7(y8(z8({8(P0)s7)z8){8)|9)€:*c5,i9-A1.R4.E3/W7/u=1x?2A97>;:{F:F?>{I>mH?DBAMDB~ODJHHOMLhSOQPPPQQSRRUTTŠaW\YXx_[…c[]]]^^^b_^___ddd‘pghhh‚mhkjijjjrksmnnnrpopppsssuttwwwyyy~{}}}“…‚Ÿ‰ƒ Š„………‹ˆ‡£ˆ‰‰‰œŽ‰ŒŠŠŒŒŠ‹‹¡‘¦“°˜“”””¯š”—••›˜•œ—–™–———˜˜˜žš˜Ÿ›™ žœžžž¡ Ÿ¡¡¡¢¢¢¨¤¢¹¨¤¦¦¦®§¦§§§¼¯¬­­­´°­µ±®²¯¯¶²¯´²±º¶²³³³µµ´µµµ½¹µ¶¶¶¹¸¶½¹¶½½¼ÅÀ½ÆÁ¾¿¿¿ÀÀÀÍÃÀÁÁÁÂÂÂÆÅÃÄÄÄÅÅÅÆÆÆÇÇÇÌÊÉÕËÉËËËÑÑÑÒÒÒÓÓÓÕÕÔÙÖÖ×××ÛÛÛÝÜÜãÝÜÞÞÞßßßáááãããäääæææçææçççéééììëìììïííîîîïïïòðïðððñññòòòôóòõõõööö÷÷÷øøøùùùúúúûúúûûûüüü°št@4Lhޤ´´¢‹O\b$8Pr”©·¸ª–aKxŒ_ *?VŸ®Nd†€cpI 0M}„Wƒ…fm¡µ’:1n{|wFv­¸³³¶u2!5ˆyziA‹²²ž“¢´¯US§yxj?‹±¥]GRr•ª·¸yxj>‡¦E-BY°yzk7l—`, %3Jg§~‚Z+Da‹˜T)9h™zC&6OoŠe" [ ¨q. *>;'#Q›´«X( <‘°œH/s¬¸‰=^£´ðÀ€Àðü( @m,6#>%D&K'O(R)P) Y+ q0!2%"`."8'#g1#4($l2%p3%q3%q4%0'&a0&r4&t5&u5&v5&e2'p4't4't5'u5'u6'v6'w6'x6'/)(r6(v6(x7(Y3)z8)v8*l6+2--m:-v;-w>1543Q93v@3lA7vC8;:9wF:><;N?<VA=?>>@??zK@AAAoJAFFF~SHbNIKKKnQKLLLrTMONNPNNZPQQQSRRVVUdXVXWWaW…aX[ZZwa\d_^‰e^i_```Šiabbbedceedhhh‹ph‘qh}mi’pijjjlkkƒplrlšwmoooqpoppp•xqwrsssyvuwwwyyy˜€{œ{}}}~~…~Ž„š…¤ˆ…‚‚ƒƒƒ¡Œ‡‰ˆˆŠ‰ŠŠŠŽŒŠ•ŽŽŽŽ“Ž¡“š‘—“—”‘ª—’“““•••——•—––›™—©š—š™™ž›™›››ŸŸŸ¦¢Ÿ±¢Ÿ¡¡¡°§£¤¤¤¦¥¤§¥¤«¦¤¬¨¥¦¦¦§§§©©©°«©²®«·®¬³¯¬­­­´°­®®®µ±®¶²¯°°°·³°¸³°²²²¸´²¹µ²Áµ²º¶³´´´·µ´º·´»·´¼¸µ¶¶¶º·¶¸¸¶»¸¶½¹¶¾º¶¸¸¸¿»¸Å»¹ººº»»»¼¼¼¾½½ËÀ½¾¾¾¿¿¿ÁÁ¿ÃÀÀÂÂÂÄÄÄÅÄÄÅÆÄÅÅÅÈÈÈÑÊÉÊÊÊÕÎÌÍÍÍÒÎÍÏÏÏÐÐÐÑÐÐÑÑÑÔÓÓÔÔÔÛÖÔÖÕÕÖÖÖÚ×Ö×××ÚÚÚÞÜÜÞÞÞãßÞàààáááâââæããäääéåäæææçççèèèëèèéééêêêìêêëëëíííîîîïïïðððòñðòòòóóóôôôöôôõõõööö÷÷÷øøøùøøùùùúùùúúúûúúûûûüüüýýýöà¶jj…¿ÞôüëÍybO =iÉæøóÙ™W‚Åa"Hu ÔìüøãÃih®ª²Z!.S¹ÚñþýíÒW•·¡›²Z% 9bŒÆâöõÝ«Xx·¥›ŸÀaBm˜ÐêüüéÉnc¢´Ÿª¯zq2 !*Nw°ÙóøÜV‹·¡¨®ˆŠÝúßr-% 3]Øøâwtµª››¡·Ž‡ÑõÓ\#%XÂ󘚴Ÿ¡´›z¼ðþ½F' D¶ï”®¢ªz™ëý÷:  E¶ï“¤¡–I£íåv- N¿ñ“¤¨}I»ñÙ`')uÕö“¤¨Hºðþðçëöþ¸YÄðþ“¤¨HºðõÞ¾—¦Òìüþþ“¤¨HºðíÍn65S|ºÜ󓤨HºðüÉJ' 9dÉæø“¤¨HºðÖ<%"DpžÕíý“¤¨Hºñès0!.S»Ý󓤨DžäüÛe(! ;gÉæø“¤³†9p°ÚñþÈQ("Gu ×íý“§²‘M8bŒÆâöù©> .U„Çí™­o0@m˜Îêüî~1% K ç•L  *Nw«Øïúï×’7%%?Ëóçƒ4%% 3X…¿Ð¶P& +_Ïõál,$ =g^/#A¬ïþÌT($ ! 1€äüû©>  )[Ñöò‰4 #C±ðþÛk,1{çüÊR(%+_ÔöþœfÁñþÿðÿÿÀÿÿ€þøðÀ€€À€ð€ø€þ€ÿ€€|À€xó€pÿ€`ÿ€`?€`€8€€€À€€àøüÿÿÿÀÿÿðÿÿøÿ(0`<5"=$H&/"S)m-*" -# X+ s0!b."o1"+%#h0#+%$l2$u3$p3%q3%q4%r4%+&&8)&V.&g2&r4&t4&t5&u5&.('O/'t5'u5'v5'u6'v6'q6(v7(y7(o7)}9),+*V2*g4*x9*:.,x;,/..70.C2.z=/211u=1l=2z@3666X?9:::}E:><<PB>~I>AAALAlKCƒODEEERFIIIŒUIƒUJKKK`OKMMM…ZPRRR\TS‡^TUUU€_WŠaWYYYs_Zt`[`^^__^Šg_a``bbaccceee‘ofhggiiillksknmmppp—xptsruuuƒxvxxxyyy}{y‡}z{{{œ‚|}}}€~}Š~€€€‚‚‚‡„‚ˆ„‚ƒƒƒ¡Š…†††Š‡‰‰‰£Ž‰ŒŠŒ‹‹‘ŽŒ’¦–‘“““•••š—•©š•œ™—˜˜˜š™˜›™˜š™™ œ™®žš››œ›¥›œœœžž žžžž¤ ž°¢Ÿ¥¡ ª¡ £££±¦£¦¦¦­©¦®«¨¶«¨©©©²­«¬¬¬³¯¬³°­´°­®®®µ±®¶±®¶²¯º²°·³°±±±½³±¸´±¶µ²¹µ²³³³º¶³½·³·¶´¸·´»·´µµµ¼¸µ½¹¶¾¹¶¾º¶···»¹·À¹·¼»¸À»¸¹¹¹¾¼¹Á½»¼¼¼¿¿¿Ç¿ÀÀÀÂÁÀÂÂÁÉÂÁÂÂÂÅÅÅÆÆÆÎÉÈÉÉÉËËËÐËËÌÌÌÎÎÎÐÐÐÕÑÐÒÒÒÔÔÔÙÖÖ×××ØØØÙÙÙÜÚÙÞÜÛÜÜÜÞÞÞàààåãâãããæääåååæææçççèççèèèéééëêêëëëììëìììíííîîîïïïðððòññòòòóóóôóóôôôõõõööö÷öö÷÷÷øøøùùùúúúûûûüüüýýýùçÈŽÀ×íúûïÕŽV)I¨æ‡¼ŸŸŸªzE¨ìûøùüãl~äø‡¼ŸŸŸªzE¨ìüðÜÓÚêøú‡¼ŸŸŸªzE¨ì÷á½tgÀÛïú‡¼ŸŸŸªzE¨ìûìÏ{=?b–Ìáôü‡¼ŸŸŸªzE¨ìúàžM+$0Lq¨Óêø‡¼ŸŸŸªzE¨ìð‹9!$ 8Y€ÁÜïú‡¼ŸŸŸªzE¨ì¦5$Cd–Ìâöü‡¼ŸŸŸªzE¨ìØc/$0Lt­Õëù‡¼ŸŸŸªzE¨ìÊW$$ :ZÃÜðû‡¼ŸŸŸªzE¢åû©G$!Ce˜Îäöü‡¼ŸŸŸª|?€Ïé÷õŠ>$$0Ov­Õëù‡¼ŸŸ¡¹o*R}ÀÚîúæu7$:ZÃÜðû‡¼Ÿ«Ÿc5 ?a‘ÉÞóûÔc&!Ce˜Ïéú‡¿°D $*JmžÐå÷ÆN$$0R€Óô¯\%$ 4Rx¸×ìùöÜf,! .eÏöF <]…ÇÜðúúéÌv=)6çûçu7!$Ee˜ÌâôúïÚ–M'#(TÄðÙc-#0Ot¨Ð×½a+ !A”ìûÆQ$# 8Yvh=$$6jÚøù—B$ 1!!%TÄóçy7!!!"A’ìûÔ`&$6jÚøÆQ&"%TÅóù—@$A’ìûèy7!$6jÚøÔ`&!%SÄóÆQ'$A”ìûû—•äùÿÿðÿÿÿÿÀÿÿÿÿ€ÿÿþ?ÿÿøÿÿðÿÿÀÿÿ€þøðà€ÀàÀøÀþÀÿÿÀÿÿÀÀÿÿðÀÿÿüÀÿÿþÀþÿƒÀøÿïÀðÿÿÀÀÿÿÀ€ÿÀ€ÿÀ€ÿÀ€ÿÀàÿÀø?ÀüÀÿÀ?ÀÀðÀðÀÀÀðüþÿÿ€ÿÿàÿÿø?ÿÿüÿÿÿÿÿÿÿÀÿÿÿÿðÿÿÿÿø?ÿÿapptools-4.1.0/docs/source/permissions/0000755000175100001440000000000011674464005021150 5ustar ischnellusers00000000000000apptools-4.1.0/docs/source/permissions/Introduction.rst0000644000175100001440000002104711674464005024367 0ustar ischnellusers00000000000000Permissions Framework - Introduction ==================================== The Permissions Framework is a component of the Enthought Tool Suite that provides developers with the facility to limit access to parts of an application unless the user is appropriately authorised. In other words it enables and disables different parts of the GUI according to the identity of the user. The framework includes an API to allow it to be integrated with an organisation's existing security infrastructure, for example to look users up in a corporate LDAP directory. The framework is completely configurable. Alternate implementations of all major components can be provided if necessary. The default implementations provide a simple local filesystem user database and allows roles to be defined and assigned to users. The framework **does not** provide any facility for protecting access to data. It is not possible to implement such protection in Python and using the file security provided by a typical operating system. Framework Concepts ------------------ The following are the concepts supported by the framework. - Permission A permission is the basic tool that a developer uses to specify that access to a part of the application should be restricted. If the current user has the permission then access is granted. A permission may be attached to a PyFace action, to an item of a TraitsUI view, or to a GUI toolkit specific widget. When the user is denied access, the corresponding GUI control is disabled or completely hidden. - User Each application has a current user who is either *authorised* or *unauthorised*. In order to become authorised a user must identify themselves and authenticate that identity. An arbitrary piece of data (called a blob) can be associated with an authorised user which (with user manager support) can be stored securely. This might be used, for example, to store sensitive user preferences, or to implement a roaming profile. - User Manager The user manager is responsible for authorising the current user and, therefore, defines how that is done. It also provides information about the user population to the policy manager. It may also, optionally, provide the ability to manage the user population (eg. add or delete users). The user manager must either maintain a persistent record of the user population, or interface with an external user database or directory service. The default user manager uses password based authorisation. The user manager persists its data in a user database. The default user manager provides an API so that different implementations of the user database can be used (for example to store the data in an RDBMS, or to integrate with an existing directory service). A default user database is provided that pickles the data in a local file. - Policy Manager The policy manager is responsible for assigning permissions to users and for determining the permissions assigned to the current user. To do this it must maintain a persistent record of those assignments. The default policy manager supplied with the framework uses roles to make it easier for an administrator to manage the relationships between permissions and users. A role is defined as a named set of permissions, and a user may have one or more roles assigned to them. The policy manager persists its data in a policy database. The default policy manager provides an API so that different implementations of the policy database can be used (for example to store the data in an RDBMS). A default policy database is provided that pickles the data in a local file. - Permissions Manager The permissions manager is a singleton object used to get and set the current policy and user managers. Framework APIs -------------- The APIs provided by the permissions framework can be split into the following groups. - `Application API`_ This part of the API is used by application developers. - `Policy Manager API`_ This is the interface that an alternative policy manager must implement. The need to implement an alternative is expected to be very rare and so the API isn't covered further. See the definition of the IPolicyManager interface for the details. - `Default Policy Manager Data API`_ This part of the API is used by developers to store the policy's persistent data in a more secure location (eg. on a remote server) than that provided by the default implementation. - `User Manager API`_ This is the interface that an alternative user manager must implement. The need to implement an alternative is expected to be very rare and so the API isn't covered further. See the definition of the IUserManager interface for the details. - `Default User Manager Data API`_ This part of the API is used by developers to store the user database in a more secure location (eg. on a remote server) than that provided by the default implementation. The complete API_ documentation is available as endo generated HTML. What Do I Need to Reimplement? ------------------------------ The architecture of the permissions framework comprises several layers, each of which can reimplemented to meet the requirements of a particular environment. Hopefully the following questions and answers will clarify what needs to be reimplemented depending on your environment. Q: Do you want to use roles to group permissions and assign them to users? A: If yes then use the supplied PolicyManager, otherwise provide your own IPolicyManager implementation. Q: Do you want users to be authenticated using a password? A: If yes then use the supplied UserManager, otherwise provide your own IUserManager implementation. Q: Does the IUser interface allow you to store all the user specific information you need? A: If yes then use the supplied UserDatabase, otherwise provide your own IUserDatabase implementation. Q: Do you want to store your user accounts as pickled data in a local file? A: If yes then use the supplied default, otherwise provide UserDatabase with your own IUserStorage implementation. Q: Do you want to store your policy data (ie. roles and role assignments) as pickled data in a local file? A: If yes then use the supplied default, otherwise provide PolicyManager with your own IPolicyStorage implementation. Deploying Alternative Managers ------------------------------ The permissions framework will first try to import the different managers from the ``apptools.permissions.external`` namespace. The default managers are only used if no alternative was found. Therefore, alternative managers should be deployed as an egg containing that namespace. Specifically the framework looks for the following classes: ``PolicyManager`` from ``apptools.permissions.external.policy_manager`` ``PolicyStorage`` from ``apptools.permissions.external.policy_storage`` ``UserDatabase`` from ``apptools.permissions.external.user_database`` ``UserManager`` from ``apptools.permissions.external.user_manager`` ``UserStorage`` from ``apptools.permissions.external.user_storage`` The example server is such a package that provides PolicyStorage and UserStorage implementations that use an XML-RPC based server to provide remote (and consequently more secure) policy and user databases. Using the Default Storage Implementations ----------------------------------------- The default policy and user managers both (again by default) persist their data as pickles in local files called ``ets_perms_policydb`` and ``ets_perms_userdb`` respectively. By default these are stored in the application's home directory (ie. that returned by ``ETSConfig.application_home``). Note that this directory is normally in the user's own directory structure whereas it needs to be available to all users of the application. If the ``ETS_PERMS_DATA_DIR`` environment variable is set then its value is used instead. The directory must be writeable by all users of the application. It should be restated that the default implementations do *not* provide secure access to the permissions and user data. They are useful in a cooperative environment and as working examples. .. _API: api/index.html .. _`Application API`: ApplicationAPI.html .. _`Policy Manager API`: https://svn.enthought.com/enthought/browser/AppTools/trunk/enthought/permissions/i_policy_manager.py .. _`Default Policy Manager Data API`: DefaultPolicyManagerDataAPI.html .. _`User Manager API`: https://svn.enthought.com/enthought/browser/AppTools/trunk/enthought/permissions/i_user_manager.py .. _`Default User Manager Data API`: DefaultUserManagerDataAPI.html apptools-4.1.0/docs/source/permissions/ApplicationAPI.rst0000644000175100001440000002530611674464005024505 0ustar ischnellusers00000000000000Application API =============== This section provides an overview of the part of the ETS Permissions Framework API used by application developers. The `Permissions Framework example`_ demonstrates the API in use. An application typically uses the API to do the following: - define permissions - apply permissions - user authentication - getting and setting user data - integrate management actions. Defining Permissions -------------------- A permission is the object that determines the user's access to a part of an application. While it is possible to apply the same permission to more than one part of an application, it is generally a bad idea to do so as it makes it difficult to separate them at a later date. A permission has an id and a human readable description. Permission ids must be unique. By convention a dotted notation is used for ids to give them a structure. Ids should at least be given an application or plugin specific prefix to ensure their uniqueness. Conventionally all an applications permissions are defined in a single ``permissions.py`` module. The following is an extract of the example's ``permissions.py`` module:: from apptools.permissions.api import Permission # Add a new person. NewPersonPerm = Permission(id='ets.permissions.example.person.new', description=u"Add a new person") # Update a person's age. UpdatePersonAgePerm = Permission(id='ets.permissions.example.person.age.update', description=u"Update a person's age") # View or update a person's salary. PersonSalaryPerm = Permission(id='ets.permissions.example.person.salary', description=u"View or update a person's salary") Applying Permissions -------------------- Permissions are applied to different parts of an applications GUI. When the user has been granted a permission then the corresponding part of the GUI is displayed normally. When the user is denied a permission then the corresponding part of the GUI is disabled or completely hidden. Permissions can be applied to TraitsUI view items and to any object which can be wrapped in a ``SecureProxy``. TraitsUI View Items ................... Items in TraitsUI views have ``enabled_when`` and ``visible_when`` traits that are evaluated to determine if the item should be enabled or visible respectively. These are used to apply permissions by storing the relevant permissions in the model so that they are available to the view. The ``enabled_when`` and ``visible_when`` traits then simply reference the permission's ``granted`` trait. The ``granted`` trait automatically reflects whether or not the user currently has the corresponding permission. In order for the view to be correctly updated when the user's permissions change (ie. when they become authenticated) the view must use the ``SecureHandler`` handler. This handler is a simple sub-class of the standard Traits ``Handler`` class. The following extract from the example shows a default view of the ``Person`` object that enables the ``age`` item when the user has the ``UpdatePersonAgePerm`` permission and shows the ``salary`` item when the user has the ``PersonSalaryPerm`` permission:: from apptools.permissions.api import SecureHandler from traits.api import HasTraits, Int, Unicode from traitsui.api import Item, View from permissions import UpdatePersonAgePerm, PersonSalaryPerm class Person(HasTraits): """A simple example of an object model""" # Name. name = Unicode # Age in years. age = Int # Salary. salary = Int # Define the default view with permissions attached. age_perm = UpdatePersonAgePerm salary_perm = PersonSalaryPerm traits_view = View( Item(name='name'), Item(name='age', enabled_when='object.age_perm.granted'), Item(name='salary', visible_when='object.salary_perm.granted'), handler=SecureHandler) Wrapping in a SecureProxy ......................... Any object can have permissions applied by wrapping it in a ``SecureProxy`` object. An adapter is used that manages the enabled and visible states of the proxied object according to the current user's permissions. Otherwise the proxy behaves just like the object being proxied. Adapters are included for the following types of object: - PyFace actions - PyFace widgets **FIXME:** TODO - Qt widgets - wx widgets See `Writing SecureProxy Adapters`_ for a description of how to write adapters for other types of objects. The following extract from the example shows the wrapping of a standard PyFace action and the application of the ``NewPersonPerm`` permission:: from apptools.permissions.api import SecureProxy from permissions import NewPersonPerm ... def _new_person_action_default(self): """Trait initializer.""" # Create the action and secure it with the appropriate permission. act = Action(name='New Person', on_perform=self._new_person) act = SecureProxy(act, permissions=[NewPersonPerm]) return act A ``SecureProxy`` also accepts a ``show`` argument that, when set to ``False``, hides the object when it becomes disabled. Authenticating the User ----------------------- The user manager supports the concept of the current user and is responsible for authenticating the user (and subsequently unauthorising the user if required). The code fragment to authenticate the current user is:: from apptools.permissions.api import get_permissions_manager get_permissions_Manager().user_manager.authenticate_user() Unauthorising the current user is done using the ``unauthenticate_user()`` method. As a convenience two PyFace actions, called ``LoginAction`` and ``LogoutAction``, are provided that wrap these two methods. As a further convenience a PyFace menu manager, called ``UserMenuManager``, is provided that contains all the user and management actions (see below) in the permissions framework. This is used by the example. The user menu, login and logout actions can be imported from ``apptools.permissions.action.api``. Getting and Setting User Data ----------------------------- The user manager has a ``user`` trait that is an object that implements the ``IUser`` interface. It is only valid once the user has been authenticated. The ``IUser`` interface has a ``blob`` trait that holds any binary data (as a Python string). The data will be read when the user is authenticated. The data will be written whenever it is changed. Integrating Management Actions ------------------------------ Both policy and user managers can provide actions that provide access to various management functions. Both have a ``management_actions`` trait that is a list of PyFace actions that invoke appropriate dialogs that allow the user to manage the policy and the user population appropriately. User managers also have a ``user_actions`` trait that is a list of PyFace actions that invoke appropriate dialogs that allow the user to manage themselves. For example, the default user manager provides an action that allows a user to change their password. The default policy manager provides actions that allows roles to be defined in terms of sets of permissions, and allows users to be assigned one or more roles. The default user manager provides actions that allows users to be added, modified and deleted. A user manager that integrates with an enterprise's secure directory service may not provide any management actions. All management actions have appropriate permissions attached to them. Writing SecureProxy Adapters ---------------------------- ``SecureProxy`` will automatically handle most of the object types you will want to apply permissions to. However it is possible to implement additional adapters to support other object types. To do this you need to implement a sub-class of ``AdapterBase`` and register it. Adapters tend to be one of two styles according to how the object's enabled and visible states are changed. If the states are changed via attributes (typically Traits based objects) then the adapter will cause a proxy to be created for the object. If the states are changed via methods (typically toolkit widgets) then the adapter will probably modify the object itself. We will refer to these two styles as wrapping adapters and patching adapters respectively. The following gives a brief overview of the ``AdapterBase`` class: ``proxied`` This instance attribute is a reference to the original object. ``register_adapter(adapter, type, type, ...)`` This is a class method that is used to register your adapter and one or more object types that it handles. ``adapt()`` This is a method that should be reimplemented by patching adapters. (The default implementation will cause a proxy to be created for wrapping adapters.) This is where any patching of the ``proxied`` attribute is done. The object returned will be returned by ``SecureProxy()`` and would normally be the patched object - but can be any object. ``setattr(name, value)`` This method should be reimplemented by wrapping adapters to intercept the setting of relevant attributes of the ``proxied`` object. The default implementation should be used as the fallback for irrelevant attributes. ``get_enabled()`` This method must be reimplemented to return the current enabled state. ``set_enabled(value)`` This method must be reimplemented to set the enabled state to the given value. ``update_enabled(value)`` This method is called by your adapter to set the desired value of the enabled state. The actual state set will depend on the current user's permissions. ``get_visible()`` This method must be reimplemented to return the current visible state. ``set_visible(value)`` This method must be reimplemented to set the visible state to the given value. ``update_visible(value)`` This method is called by your adapter to set the desired value of the visible state. The actual state set will depend on the current user's permissions. The ``AdapterBase`` class is defined in `adapter_base.py`_. The `PyFace action adapter`_ is an example of a wrapping adapter. The `PyQt widget adapter`_ is an example of a patching adapter. .. _`Permissions Framework example`: https://svn.enthought.com/enthought/browser/AppTools/trunk/examples/permissions/application/ .. _`adapter_base.py`: https://svn.enthought.com/enthought/browser/AppTools/trunk/enthought/permissions/adapter_base.py .. _`PyFace action adapter`: https://svn.enthought.com/enthought/browser/AppTools/trunk/enthought/permissions/adapters/pyface_action.py .. _`PyQt widget adapter`: https://svn.enthought.com/enthought/browser/AppTools/trunk/enthought/permissions/adapters/qt4_widget.py apptools-4.1.0/docs/source/permissions/DefaultUserManagerDataAPI.rst0000644000175100001440000000725711674464005026557 0ustar ischnellusers00000000000000Default User Manager Data API ============================= This section provides an overview of the part of the ETS Permissions Framework API used by developers who want to store a user database in a more secure location (eg. a remote server) than that provided by the default implementation. The API is defined by the default user manager which uses password based authorisation. If this API isn't sufficiently flexible, or if another method of authorisation is used (biometrics for example) then an alternative user manager should be implemented. The API is fully defined by the `IUserDatabase interface`_. This allows user databases to be implemented that extend the `IUser interface`_ and store additional user related data. If the user database is being persisted in secure storage (eg. a remote RDBMS) then this could be used to store sensitive data (eg. passwords for external systems) that shouldn't be stored as ordinary preferences. In most cases there will be no requirement to store additional user related data than that defined by ``IUser`` so the supplied `UserDatabase implementation`_ (which provides all the GUI code required to implement the `IUserDatabase interface`_) can be used. The `UserDatabase implementation`_ delegates the access to the user database to an object implementing the `IUserStorage interface`_. The default implementation of this interface stores the user database as a pickle in a local file. Overview of IUserStorage ------------------------ The `IUserStorage interface`_ defines a number of methods that must be implemented to read and write to the user database. The methods are designed to be implemented using simple SQL statements. In the event of an error a method must raise the ``UserStorageError`` exception. The string representation of the exception is used as an error message that is displayed to the user. Overview of IUserDatabase ------------------------- The `IUserDatabase interface`_ defines a set of ``Bool`` traits, all beginning with ``can_``, that describe the capabilities of a particular implementation. For example, the ``can_add_user`` trait is set by an implementation if it supports the ability to add a new user to the database. Each of these capability traits has a corresponding method which has the same name except for the ``can_`` prefix. The method only needs to be implemented if the corresponding traits is ``True``. The method, for example ``add_user()`` is called by the user manager to implement the capability. The interface has two other methods. The ``bootstrapping()`` method is called by the user manager to determine if the database is bootstrapping. Typically this is when the database is empty and no users have yet been defined. The permissions framework treats this situation as a special case and is able to relax the enforcement of permissions to allow users and permissions to be initially defined. The ``user_factory()`` method is called by the user manager to create a new user object, ie. an object that implements the `IUser interface`_. This allows an implementation to extend the `IUser interface`_ and store additional user related data in the object if the ``blob`` trait proves insufficient. .. _`IUser interface`: https://svn.enthought.com/enthought/browser/AppTools/trunk/enthought/permissions/i_user.py .. _`IUserDatabase interface`: https://svn.enthought.com/enthought/browser/AppTools/trunk/enthought/permissions/default/i_user_database.py .. _`IUserStorage interface`: https://svn.enthought.com/enthought/browser/AppTools/trunk/enthought/permissions/default/i_user_storage.py .. _`UserDatabase implementation`: https://svn.enthought.com/enthought/browser/AppTools/trunk/enthought/permissions/default/user_database.py apptools-4.1.0/docs/source/permissions/DefaultPolicyManagerDataAPI.rst0000644000175100001440000000244511674464005027072 0ustar ischnellusers00000000000000Default Policy Manager Data API =============================== This section provides an overview of the part of the ETS Permissions Framework API used by developers who want to store a policy manager's persistent data in a more secure location (eg. a remote server) than that provided by the default implementation. The API is defined by the default policy manager which uses roles to make it easier to assign permissions to users. If this API isn't sufficiently flexible, or if roles are inappropriate, then an alternative policy manager should be implemented. The API is fully defined by the `IPolicyStorage interface`_. The default implementation of this interface stores the policy database as a pickle in a local file. Overview of IPolicyStorage -------------------------- The `IPolicyStorage interface`_ defines a number of methods that must be implemented to read and write to the policy database. The methods are designed to be implemented using simple SQL statements. In the event of an error a method must raise the ``PolicyStorageError`` exception. The string representation of the exception is used as an error message that is displayed to the user. .. _`IPolicyStorage interface`: https://svn.enthought.com/enthought/browser/AppTools/trunk/enthought/permissions/default/i_policy_storage.py apptools-4.1.0/docs/source/preferences/0000755000175100001440000000000011674464005021076 5ustar ischnellusers00000000000000apptools-4.1.0/docs/source/preferences/Preferences.rst0000644000175100001440000002505711674464005024102 0ustar ischnellusers00000000000000Preferences =========== The preferences package provides a simple API for managing application preferences. The classes in the package are implemented using a layered approach where the lowest layer provides access to the raw preferences mechanism and each layer on top providing more convenient ways to get and set preference values. The Basic Preferences Mechanism =============================== Lets start by taking a look at the lowest layer which consists of the IPreferences_ interface and its default implementation in the Preferences_ class. This layer implements the basic preferences system which is a hierarchical arrangement of preferences 'nodes' (where each node is simply an object that implements the IPreferences_ interface). Nodes in the hierarchy can contain preference settings and/or child nodes. This layer also provides a default way to read and write preferences from the filesystem using the excellent ConfigObj_ package. This all sounds a bit complicated but, believe me, it isn't! To prove it (hopefully) lets look at an example. Say I have the following preferences in a file 'example.ini':: [acme.ui] bgcolor = blue width = 50 ratio = 1.0 visible = True [acme.ui.splash_screen] image = splash fgcolor = red I can create a preferences hierarchy from this file by:: >>> from apptools.preferences.api import Preferences >>> preferences = Preferences(filename='example.ini') >>> preferences.dump() Node() {} Node(acme) {} Node(ui) {'bgcolor': 'blue', 'ratio': '1.0', 'width': '50', 'visible': 'True'} Node(splash_screen) {'image': 'splash', 'fgcolor': 'red'} The 'dump' method (useful for debugging etc) simply 'pretty prints' a preferences hierarchy. The dictionary next to each node contains the node's actual preferences. In this case, the root node (the node with no name) is the preferences object that we created. This node now has one child node 'acme', which contains no preferences. The 'acme' node has one child, 'ui', which contains some preferences (e.g. 'bgcolor') and also a child node 'splash_screen' which also contains preferences (e.g. 'image'). To look up a preference we use:: >>> preferences.get('acme.ui.bgcolor') 'blue' If no such preferences exists then, by default, None is returned:: >>> preferences.get('acme.ui.bogus') is None True You can also specify an explicit default value:: >>> preferences.get('acme.ui.bogus', 'fred') 'fred' To set a preference we use:: >>> preferences.set('acme.ui.bgcolor', 'red') >>> preferences.get('acme.ui.bgcolor') 'red' And to make sure the preferences are saved back to disk:: >>> preferences.flush() To add a new preference value we simply set it:: >>> preferences.set('acme.ui.fgcolor', 'black') >>> preferences.get('acme.ui.fgcolor') 'black' Any missing nodes in a call to 'set' are created automatically, hence:: >>> preferences.set('acme.ui.button.fgcolor', 'white') >>> preferences.get('acme.ui.button.fgcolor') 'white' Preferences can also be 'inherited'. e.g. Notice that the 'splash_screen' node does not contain a 'bgcolor' preference, and hence:: >>> preferences.get('acme.ui.splash_screen.bgcolor') is None True But if we allow the 'inheritance' of preference values then:: >>> preferences.get('acme.ui.splash_screen.bgcolor', inherit=True) 'red' By using 'inheritance' here the preferences system will try the following preferences:: 'acme.ui.splash_screen.bgcolor' 'acme.ui.bgcolor' 'acme.bgcolor' 'bgcolor' Strings, Glorious Strings ------------------------- At this point it is worth mentioning that preferences are *always* stored and returned as strings. This is because of the limitations of the traditional '.ini' file format i.e. they don't contain any type information! Now before you start panicking, this doesn't mean that all of your preferences have to be strings! Currently the preferences system allows, strings(!), booleans, ints, longs, floats and complex numbers. When you store a non-string value it gets converted to a string for you, but you *always* get a string back:: >>> preferences.get('acme.ui.width') '50' >>> preferences.set('acme.ui.width', 100) >>> preferences.get('acme.ui.width') '100' >>> preferences.get('acme.ui.visible') 'True' >>> preferences.set('acme.ui.visible', False) >>> preferences.get('acme.ui.visible') 'False' This is obviously not terribly convenient, and so the following section discusses how we associate type information with our preferences to make getting and setting them more natural. Preferences and Types ===================== As mentioned previously, we would like to be able to get and set non-string preferences in a more convenient way. This is where the PreferencesHelper_ class comes in. Let's take another look at 'example.ini':: [acme.ui] bgcolor = blue width = 50 ratio = 1.0 visible = True [acme.ui.splash_screen] image = splash fgcolor = red Say, I am interested in the preferences in the 'acme.ui' section. I can use a preferences helper as follows:: from apptools.preferences.api import PreferencesHelper class SplashScreenPreferences(PreferencesHelper): """ A preferences helper for the splash screen. """ PREFERENCES_PATH = 'acme.ui' bgcolor = Str width = Int ratio = Float visible = Bool >>> preferences = Preferences(filename='example.ini') >>> helper = SplashScreenPreferences(preferences=preferences) >>> helper.bgcolor 'blue' >>> helper.width 100 >>> helper.ratio 1.0 >>> helper.visible True And, obviously, I can set the value of the preferences via the helper too:: >>> helper.ratio = 0.5 And if you want to prove to yourself it really did set the preference:: >>> preferences.get('acme.ui.ratio') '0.5' Using a preferences helper you also get notified via the usual trait mechanism when the preferences are changed (either via the helper or via the preferences node directly:: def listener(obj, trait_name, old, new): print trait_name, old, new >>> helper.on_trait_change(listener) >>> helper.ratio = 0.75 ratio 0.5 0.75 >>> preferences.set('acme.ui.ratio', 0.33) ratio 0.75 0.33 If you always use the same preference node as the root of your preferences you can also set the class attribute 'PreferencesHelper.preferences' to be that node and from then on in, you don't have to pass a preferences collection in each time you create a helper:: >>> PreferencesHelper.preferences = Preferences(filename='example.ini') >>> helper = SplashScreenPreferences() >>> helper.bgcolor 'blue' >>> helper.width 100 >>> helper.ratio 1.0 >>> helper.visible True Scoped Preferences ================== In many applications the idea of preferences scopes is useful. In a scoped system, an actual preference value can be stored in any scope and when a call is made to the 'get' method the scopes are searched in order of precedence. The default implementation (in the ScopedPreferences_ class) provides two scopes by default: 1) The application scope This scope stores itself in the 'ETSConfig.application_home' directory. This scope is generally used when *setting* any user preferences. 2) The default scope This scope is transient (i.e. it does not store itself anywhere). This scope is generally used to load any predefined default values into the preferences system. If you are happy with the default arrangement, then using the scoped preferences is just like using the plain old non-scoped version:: >>> from apptools.preferences.api import ScopedPreferences >>> preferences = ScopedPreferences(filename='example.ini') >>> preferences.load('example.ini') >>> p.dump() Node() {} Node(application) {} Node(acme) {} Node(ui) {'bgcolor': 'blue', 'ratio': '1.0', 'width': '50', 'visible': 'True'} Node(splash_screen) {'image': 'splash', 'fgcolor': 'red'} Node(default) {} Here you can see that the root node now has a child node representing each scope. When we are getting and setting preferences using scopes we generally want the following behaviour: a) When we get a preference we want to look it up in each scope in order. The first scope that contains a value 'wins'. b) When we set a preference, we want to set it in the first scope. By default this means that when we set a preference it will be set in the application scope. This is exactly what we want as the application scope is the scope that is persistent. So usually, we just use the scoped preferences as before:: >>> preferences.get('acme.ui.bgcolor') 'blue' >>> preferences.set('acme.ui.bgcolor', 'red') >>> preferences.dump() Node() {} Node(application) {} Node(acme) {} Node(ui) {'bgcolor': 'red', 'ratio': '1.0', 'width': '50', 'visible': 'True'} Node(splash_screen) {'image': 'splash', 'fgcolor': 'red'} Node(default) {} And, conveniently, preference helpers work just the same with scoped preferences too:: >>> PreferencesHelper.preferences = ScopedPreferences(filename='example.ini') >>> helper = SplashScreenPreferences() >>> helper.bgcolor 'blue' >>> helper.width 100 >>> helper.ratio 1.0 >>> helper.visible True Accessing a particular scope ---------------------------- Should you care about getting or setting a preference in a particular scope then you use the following syntax:: >>> preferences.set('default/acme.ui.bgcolor', 'red') >>> preferences.get('default/acme.ui.bgcolor') 'red' >>> preferences.dump() Node() {} Node(application) {} Node(acme) {} Node(ui) {'bgcolor': 'red', 'ratio': '1.0', 'width': '50', 'visible': 'True'} Node(splash_screen) {'image': 'splash', 'fgcolor': 'red'} Node(default) {} Node(acme) {} Node(ui) {'bgcolor': 'red'} You can also get hold of a scope via:: >>> default = preferences.get_scope('default') And then perform any of the usual operations on it. Further Reading =============== So that's a quick tour around the basic useage of the preferences API. For more imformation about what is provided take a look at the API_ documentation. If you are using Envisage to build your applications then you might also be interested in the `Preferences in Envisage`_ section. .. _API: api/index.html .. _ConfigObj: http://www.voidspace.org.uk/python/configobj.html .. _IPreferences: ../../enthought/preferences/i_preferences.py .. _Preferences: ../../enthought/preferences/preferences.py .. _PreferencesHelper: ../../enthought/preferences/preferences_helper.py .. _ScopedPreferences: ../../enthought/preferences/scoped_preferences.py .. _`Preferences in Envisage`: PreferencesInEnvisage.html apptools-4.1.0/docs/source/preferences/PreferencesInEnvisage.rst0000644000175100001440000000311211674464005026037 0ustar ischnellusers00000000000000Preferences in Envisage ======================= This section discusses how an Envisage application uses the preferences mechanism. Envisage tries not to dictate too much, and so this describes the default behaviour, but you are free to override it as desired. Envisage uses the default implementation of the ScopedPreferences_ class which is made available via the application's 'preferences' trait:: >>> application = Application(id='myapplication') >>> application.preferences.set('acme.ui.bgcolor', 'yellow') >>> application.preferences.get('acme.ui.bgcolor') 'yellow' Hence, you use the Envisage preferences just like you would any other scoped preferences. It also registers itself as the default preferences node used by the PreferencesHelper_ class. Hence you don't need to provide a preferences node explicitly to your helper:: >>> helper = SplashScreenPreferences() >>> helper.bgcolor 'blue' >>> helper.width 100 >>> helper.ratio 1.0 >>> helper.visible True The only extra thing that Envisage does for you is to provide an extension point that allows you to contribute any number of '.ini' files that are loaded into the default scope when the application is started. e.g. To contribute a preference file for my plugin I might use:: class MyPlugin(Plugin): ... @contributes_to('envisage.preferences') def get_preferences(self, application): return ['pkgfile://mypackage:preferences.ini'] .. _PreferencesHelper: ../../enthought/preferences/preferences_helper.py .. _ScopedPreferences: ../../enthought/preferences/scoped_preferences.py apptools-4.1.0/docs/source/conf.py0000644000175100001440000001324111674464005020075 0ustar ischnellusers00000000000000# -*- coding: utf-8 -*- # # EnvisageCore documentation build configuration file, created by # sphinx-quickstart on Fri Jul 18 17:09:28 2008. # # This file is execfile()d with the current directory set to its containing dir. # # The contents of this file are pickled, so don't put values in the namespace # that aren't pickleable (module imports are okay, they're removed automatically). # # All configuration values have a default value; values that are commented out # serve to show the default value. import sys, os # If your extensions are in another directory, add it here. If the directory # is relative to the documentation root, use os.path.abspath to make it # absolute, like shown here. #sys.path.append(os.path.abspath('some/directory')) # General configuration # --------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. #extensions = ['sphinx.ext.autodoc'] extensions = [] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General substitutions. project = 'apptools' copyright = '2008-2011, Enthought' # The default replacements for |version| and |release|, also used in various # other places throughout the built documents. # # The short X.Y version. # The full version, including alpha/beta/rc tags. d = {} execfile(os.path.join('..', '..', 'apptools', '__init__.py'), d) version = release = d['__version__'] # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directories, that shouldn't be searched # for source files. #exclude_dirs = [] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # Options for HTML output # ----------------------- # The style sheet to use for HTML and HTML Help pages. A file of that name # must exist either in Sphinx' static/ path, or in one of the custom paths # given in html_static_path. html_style = 'default.css' # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (within the static path) to place at the top of # the sidebar. html_logo = 'e-logo-rev.png' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. html_favicon = 'et.ico' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_use_modindex = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, the reST sources are included in the HTML build as _sources/. #html_copy_source = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'AppToolsdoc' # Options for LaTeX output # ------------------------ # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, document class [howto/manual]). latex_documents = [ ('index', 'AppTools.tex', 'AppTools Documentation', 'Enthought', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True apptools-4.1.0/docs/source/index.rst0000644000175100001440000000054111674464005020436 0ustar ischnellusers00000000000000AppTools Documentation ============================================== .. toctree:: :maxdepth: 2 :glob: appscripting/* permissions/Introduction permissions/ApplicationAPI permissions/DefaultPolicyManagerDataAPI permissions/DefaultUserManagerDataAPI preferences/* scripting/* undo/* * :ref:`search` apptools-4.1.0/docs/source/appscripting/0000755000175100001440000000000011674464005021300 5ustar ischnellusers00000000000000apptools-4.1.0/docs/source/appscripting/Introduction.rst0000644000175100001440000003504111674464005024516 0ustar ischnellusers00000000000000Application Scripting Framework =============================== The Application Scripting Framework is a component of the Enthought Tool Suite that provides developers with an API that allows traits based objects to be made scriptable. Operations on a scriptable object can be recorded in a script and subsequently replayed. The framework is completely configurable. Alternate implementations of all major components can be provided if necessary. Framework Concepts ------------------ The following are the concepts supported by the framework. - Scriptable Type A scriptable type is a sub-type of ``HasTraits`` that has scriptable methods and scriptable traits. If a scriptable method is called, or a scriptable trait is set, then that action can be recorded in a script and subsequently replayed. If the ``__init__()`` method is scriptable then the creation of an object from the type can be recorded. Scriptable types can be explicitly defined or created dynamically from any sub-type of ``HasTraits``. - Scriptable API The set of a scriptable type's scriptable methods and traits constitutes the type's scriptable API. The API can be defined explicitly using the ``scriptable`` decorator (for methods) or the ``Scriptable`` wrapper (for traits). For scriptable types that are created dynamically then the API can be defined in terms of one or more types or interfaces or an explicit list of method and trait names. By default, all public methods and traits (ie. those whose name does not begin with an underscore) are part of the API. It is also possible to then explicitly exclude a list of method and trait names. - Scriptable Object A scriptable object is an instance of a scriptable type. Scriptable objects can be explicitly created by calling the scriptable type. Alternatively a non-scriptable object can be made scriptable dynamically. - Script A script is a Python script and may be a recording or written from scratch. If the creation of scriptable objects can be recorded, then it may be possible for a recording to be run directly by the Python interpreter and independently of the application that made the recording. Otherwise the application must run the script and first create any scriptable objects refered to in the script. - Binding A script runs in a namespace which is, by default, empty. If the scriptable objects refered to in a script are not created by the script (because their type's ``__init__()`` method isn't scriptable) then they must be created by the application and added to the namespace. Adding an object to the namespace is called binding. Scriptable objects whose creation can be recorded will automatically bind themselves when they are created. It also possible to bind an object factory rather than the object itself. The factory will be called, and the object created, only if the object is needed by the script when it is run. This is typically used by plugins. The name that an object is bound to need bear no relation to the object's name within the application. Names may be dotted names (eg. ``aaa.bbb.ccc``) and appropriate objects representing the intermediate parts of such a name will be created automatically. An event is fired whenever an object is bound (or when a bound factory is invoked). This allows other objects (eg. an embedded Python shell) to expose scriptable objects in other ways. - Script Manager A script manager is responsible for the recording and subsequent playback of scripts. An application has a single script manager instance which can be explicitly set or created automatically. Limitations ----------- In the current implementation scriptable Trait container types (eg. List, Dict) may only contain objects corresponding to fundamental Python types (eg. int, bool, str). API Overview ------------ This section gives an overview of the API implemented by the framework. The complete API_ documentation is available as endo generated HTML. The example_ application demonstrates some the features of the framework. Module Level Objects .................... ``get_script_manager()`` The application's script manager is returned. One will be created automatically if needed. ``set_script_manager(script_manager)`` The application's script manager will be set to ``script_manager`` replacing any existing script manager. ``scriptable`` This is a decorator used to explicitly mark methods as being scriptable. Any call to a scriptable method is recorded. If a type's ``__init__()`` method is decorated then the creation of the object will be recorded. ``Scriptable`` This is a wrapper for a trait to explicitly mark it as being scriptable. Any change to the value of the trait will be recorded. Simple reads of the trait will not be recorded unless unless the value read is bound to another scriptable trait or passed as an argument to a scriptable method. Passing ``has_side_effects=True`` when wrapping the trait will ensure that a read will always be recorded. ``create_scriptable_type(script_type, name=None, bind_policy='auto', api=None, includes=None, excludes=None, script_init=True)`` This creates a new type based on an existing type but with certain methods and traits marked as being scriptable. Scriptable objects can then be created by calling the type. ``script_type`` is the existing, non-scriptable, type. The new type will be a sub-type of it. The ``api``, ``includes`` and ``excludes`` arguments determine which methods and traits are made scriptable. By default, all public methods and traits (ie. those whose name does not begin with an underscore) are made scriptable. The ``name`` and ``bind_policy`` arguments determine how scriptable objects are bound when they are created. ``name`` is the name that an object will be bound to. It defaults to the name of ``script_type`` with the first character forced to lower case. ``name`` may be a dotted name, eg. ``aaa.bb.c``. ``bind_policy`` determines what happens if an object is already bound to the name. If it is ``auto`` then a numerical suffix will be added to the name of the new object. If it is ``unique`` then an exception will be raised. If it is ``rebind`` then the object currently bound to the name will be unbound. ``api`` is a class or interface (or a list of classes or interfaces) that is used to provide the names of the methods and traits to be made scriptable. The class or interface effectively defines the scripting API. If ``api`` is not specified then ``includes`` is a list of method and trait names that are made scriptable. If ``api`` and ``includes`` are not specified then ``excludes`` is a list of method and trait names that are *not* made scriptable. If ``script_init`` is set then the ``__init__()`` method is made scriptable irrespective of the ``api``, ``includes`` and ``excludes`` arguments. If ``script_init`` is not set then objects must be explicitly bound and ``name`` and ``bind_policy`` are ignored. ``make_object_scriptable(obj, api=None, includes=None, excludes=None)`` This takes an existing unscriptable object and makes it scriptable. It works by calling ``create_scriptable_type()`` on the the objects existing type and replacing that existing type with the new scriptable type. See the description of ``create_scriptable_type()`` for an explanation of the ``api``, ``includes`` and ``excludes`` arguments. ScriptManager ............. The ``ScriptManager`` class is the default implementation of the ``IScriptManager`` interface. ``bind_event`` This event is fired whenever an object is bound or unbound. The event's argument implements the ``IBindEvent`` interface. ``recording`` This trait is set if a script is currently being recorded. It is updated automatically by the script manager. ``script`` This trait contains the text of the script currently being recorded (or the last recorded script if one is not being currently recorded). It is updated automatically by the script manager. ``script_updated`` This event is fired whenever the ``script`` trait is updated. The event's argument is the script manager. ``bind(self, obj, name=None, bind_policy='unique', api=None, includes=None, excludes=None)`` This method makes an object scriptable and binds it to a name. See the description of ``create_scriptable_type()`` for an explanation of the ``api``, ``includes``, ``excludes``, ``name`` and ``bind_policy`` arguments. ``bind_factory(self, factory, name, bind_policy='unique', api=None, includes=None, excludes=None)`` This method binds an object factory to a name. The factory is called to create the object (and make it scriptable) only when the object is needed by a running script. See the description of ``create_scriptable_type()`` for an explanation of the ``name`` and ``bind_policy`` arguments. ``run(self, script)`` This method runs a script in a namespace containing all currently bound objects. ``script`` is any object that can be used by Python's ``exec`` statement including a string or a file-like object. ``run_file(self, file_name)`` This method runs a script in a namespace containing all currently bound objects. ``file_name`` is the name of a file containing the script. ``start_recording(self)`` This method starts the recording of a script. ``stop_recording(self)`` This method stops the recording of the current script. IBindEvent .......... The ``IBindEvent`` interface defines the interface that is implemented by the object passed when the script manager's ``bind_event`` is fired. ``name`` This trait is the name being bound or unbound. ``obj`` This trait is the obj being bound to ``name`` or None if ``name`` is being unbound. StartRecordingAction .................... The ``StartRecordingAction`` class is a canned PyFace action that starts the recording of changes to scriptable objects to a script. StopRecordingAction ................... The ``StopRecordingAction`` class is a canned PyFace action that ends the recording of changes to scriptable objects to a script. Implementing Application Scripting ---------------------------------- The key part of supporting application scripting is to design an appropriate scripting API and to ensure than the application itself uses the API so that changes to the data can be recorded. The framework provides many ways to specify the scripting API. Which approach is appropriate in a particular case will depend on when it is a new application, or whether scripting is being added to an existing application, and how complex the application's data model is. Static Specification .................... A scripting API is specified statically by the explicit use of the ``scriptable`` decorator and the ``Scriptable`` trait wrapper. For example:: from apptools.appscripting.api import scriptable, Scriptable from traits.api import HasTraits, Int, Str class DataModel(HasTraits): foo = Scriptable(Str) bar = Scriptable(Int, has_side_effects=True) @scriptable def baz(self): pass def weeble(self) pass # Create the scriptable object. It's creation won't be recorded because # __init__() isn't decorated. obj = DataModel() # These will be recorded. obj.foo = '' obj.bar = 10 obj.baz() # This will not be recorded. obj.weeble() # This won't be recorded unless 'f' is passed to something that is # recorded. f = obj.foo # This will be recorded because we set 'has_side_effects'. b = obj.bar Dynamic Specification ..................... A scripting API can also be specified dynamically. The following example produces a scriptable object with the same scriptable API as above (with the exception that ``has_side_effects`` cannot be specified dynamically):: from apptools.appscripting.api import create_scriptable_type from traits.api import HasTraits, Int, Str class DataModel(HasTraits): foo = Str bar = Int def baz(self): pass def weeble(self) pass # Create a scriptable type based on the above. ScriptableDataModel = create_scriptable_type(DataModel, excludes=['weeble']) # Now create scriptable objects from the scriptable type. Note that each # object has the same type. obj1 = ScriptableDataModel() obj2 = ScriptableDataModel() Instead we could bypass the type and make the objects themselves scriptable as follows:: from apptools.appscripting.api import make_object_scriptable from traits.api import HasTraits, Int, Str class DataModel(HasTraits): foo = Str bar = Int def baz(self): pass def weeble(self) pass # Create unscriptable objects. obj1 = DataModel() obj2 = DataModel() # Now make the objects scriptable. Note that each object has a different # type, each a sub-type of 'DataModel'. make_object_scriptable(obj1, excludes=['weeble']) make_object_scriptable(obj2, excludes=['weeble']) With a more sophisticated design we may choose to specify the scriptable API as an interface as follows:: from apptools.appscripting.api import make_object_scriptable from traits.api import HasTraits, Int, Interface, Str class DataModel(HasTraits): foo = Str bar = Int def baz(self): pass def weeble(self) pass class IScriptableDataModel(Interface): foo = Str bar = Int def baz(self): pass # Create an unscriptable object. obj = DataModel() # Now make the object scriptable. make_object_scriptable(obj, api=IScriptableDataModel) Scripting __init__() .................... Making a type's ``__init__()`` method has advantages and disadvantages. It means that the creation of scriptable objects will be recorded in a script (along with the necessary ``import`` statements). This means that the script can be run independently of your application by the standard Python interpreter. The disadvantage is that, if you have a complex data model, with many interdependencies, then defining a complete and consistent scripting API that allows a script to run independently may prove difficult. In such cases it is better to have the application create and bind the scriptable objects itself. .. _API: api/index.html .. _example: https://svn.enthought.com/enthought/browser/AppTools/trunk/examples/appscripting/ apptools-4.1.0/docs/source/undo/0000755000175100001440000000000011674464005017542 5ustar ischnellusers00000000000000apptools-4.1.0/docs/source/undo/Introduction.rst0000644000175100001440000002356511674464005022770 0ustar ischnellusers00000000000000Undo Framework ============== The Undo Framework is a component of the Enthought Tool Suite that provides developers with an API that implements the standard pattern for do/undo/redo commands. The framework is completely configurable. Alternate implementations of all major components can be provided if necessary. Framework Concepts ------------------ The following are the concepts supported by the framework. - Command A command is an application defined operation that can be done (i.e. executed), undone (i.e. reverted) and redone (i.e. repeated). A command operates on some data and maintains sufficient state to allow it to revert or repeat a change to the data. Commands may be merged so that potentially long sequences of similar commands (e.g. to add a character to some text) can be collapsed into a single command (e.g. to add a word to some text). - Macro A macro is a sequence of commands that is treated as a single command when being undone or redone. - Command Stack A command is done by pushing it onto a command stack. The last command can be undone and redone by calling appropriate command stack methods. It is also possible to move the stack's position to any point and the command stack will ensure that commands are undone or redone as required. A command stack maintains a *clean* state which is updated as commands are done and undone. It may be explicitly set, for example when the data being manipulated by the commands is saved to disk. Canned PyFace actions are provided as wrappers around command stack methods to implement common menu items. - Undo Manager An undo manager is responsible for one or more command stacks and maintains a reference to the currently active stack. It provides convenience undo and redo methods that operate on the currently active stack. An undo manager ensures that each command execution is allocated a unique sequence number, irrespective of which command stack it is pushed to. Using this it is possible to synchronise multiple command stacks and restore them to a particular point in time. An undo manager will generate an event whenever the clean state of the active stack changes. This can be used to maintain some sort of GUI status indicator to tell the user that their data has been modified since it was last saved. Typically an application will have one undo manager and one undo stack for each data type that can be edited. However this is not a requirement: how the command stack's in particular are organised and linked (with the user manager's sequence number) can need careful thought so as not to confuse the user - particularly in a plugin based application that may have many editors. To support this typical usage the PyFace ``Workbench`` class has an ``undo_manager`` trait and the PyFace ``Editor`` class has a ``command_stack`` trait. Both are lazy loaded so can be completely ignored if they are not used. API Overview ------------ This section gives a brief overview of the various classes implemented in the framework. The complete API_ documentation is available as endo generated HTML. The example_ application demonstrates all the major features of the framework. UndoManager ........... The ``UndoManager`` class is the default implementation of the ``IUndoManager`` interface. ``active_stack`` This trait is a reference to the currently active command stack and may be None. Typically it is set when some sort of editor becomes active. ``active_stack_clean`` This boolean trait reflects the clean state of the currently active command stack. It is intended to support a "document modified" indicator in the GUI. It is maintained by the undo manager. ``stack_updated`` This event is fired when the index of a command stack is changed. A reference to the stack is passed as an argument to the event and may not be the currently active stack. ``undo_name`` This Unicode trait is the name of the command that can be undone, and will be empty if there is no such command. It is maintained by the undo manager. ``redo_name`` This Unicode trait is the name of the command that can be redone, and will be empty if there is no such command. It is maintained by the undo manager. ``sequence_nr`` This integer trait is the sequence number of the next command to be executed. It is incremented immediately before a command's ``do()`` method is called. A particular sequence number identifies the state of all command stacks handled by the undo manager and allows those stacks to be set to the point they were at at a particular point in time. In other words, the sequence number allows otherwise independent command stacks to be synchronised. ``undo()`` This method calls the ``undo()`` method of the last command on the active command stack. ``redo()`` This method calls the ``redo()`` method of the last undone command on the active command stack. CommandStack ............ The ``CommandStack`` class is the default implementation of the ``ICommandStack`` interface. ``clean`` This boolean traits reflects the clean state of the command stack. Its value changes as commands are executed, undone and redone. It may also be explicitly set to mark the current stack position as being clean (when data is saved to disk for example). ``undo_name`` This Unicode trait is the name of the command that can be undone, and will be empty if there is no such command. It is maintained by the command stack. ``redo_name`` This Unicode trait is the name of the command that can be redone, and will be empty if there is no such command. It is maintained by the command stack. ``undo_manager`` This trait is a reference to the undo manager that manages the command stack. ``push(command)`` This method executes the given command by calling its ``do()`` method. Any value returned by ``do()`` is returned by ``push()``. If the command couldn't be merged with the previous one then it is saved on the command stack. ``undo(sequence_nr=0)`` This method undoes the last command. If a sequence number is given then all commands are undone up to an including the sequence number. ``redo(sequence_nr=0)`` This method redoes the last command and returns any result. If a sequence number is given then all commands are redone up to an including the sequence number and any result of the last of these is returned. ``clear()`` This method clears the command stack, without undoing or redoing any commands, and leaves the stack in a clean state. It is typically used when all changes to the data have been abandoned. ``begin_macro(name)`` This method begins a macro by creating an empty command with the given name. The commands passed to all subsequent calls to ``push()`` will be contained in the macro until the next call to ``end_macro()``. Macros may be nested. The command stack is disabled (ie. nothing can be undone or redone) while a macro is being created (ie. while there is an outstanding ``end_macro()`` call). ``end_macro()`` This method ends the current macro. ICommand ........ The ``ICommand`` interface defines the interface that must be implemented by any undoable/redoable command. ``data`` This optional trait is a reference to the data object that the command operates on. It is not used by the framework itself. ``name`` This Unicode trait is the name of the command as it will appear in any GUI element (e.g. in the text of an undo and redo menu entry). It may include ``&`` to indicate a keyboard shortcut which will be automatically removed whenever it is inappropriate. ``__init__(*args)`` If the command takes arguments then the command must ensure that deep copies should be made if appropriate. ``do()`` This method is called by a command stack to execute the command and to return any result. The command must save any state necessary for the ``undo()`` and ``redo()`` methods to work. It is guaranteed that this will only ever be called once and that it will be called before any call to ``undo()`` or ``redo()``. ``undo()`` This method is called by a command stack to undo the command. ``redo()`` This method is called by a command stack to redo the command and to return any result. ``merge(other)`` This method is called by the command stack to try and merge the ``other`` command with this one. True should be returned if the commands were merged. If the commands are merged then ``other`` will not be placed on the command stack. A subsequent undo or redo of this modified command must have the same effect as the two original commands. AbstractCommand ............... ``AbstractCommand`` is an abstract base class that implements the ``ICommand`` interface. It provides a default implementation of the ``merge()`` method. CommandAction ............. The ``CommandAction`` class is a sub-class of the PyFace ``Action`` class that is used to wrap commands. ``command`` This callable trait must be set to a factory that will return an object that implements ``ICommand``. It will be called when the action is invoked and the object created pushed onto the command stack. ``command_stack`` This instance trait must be set to the command stack that commands invoked by the action are pushed to. ``data`` This optional trait is a reference to the data object that will be passed to the ``command`` factory when it is called. UndoAction .......... The ``UndoAction`` class is a canned PyFace action that undoes the last command of the active command stack. RedoAction .......... The ``RedoAction`` class is a canned PyFace action that redoes the last command undone of the active command stack. .. _API: api/index.html .. _example: https://svn.enthought.com/enthought/browser/AppTools/trunk/examples/undo/ apptools-4.1.0/docs/source/scripting/0000755000175100001440000000000011674464005020577 5ustar ischnellusers00000000000000apptools-4.1.0/docs/source/scripting/introduction.rst0000644000175100001440000001513511674464005024057 0ustar ischnellusers00000000000000.. _automatic-script-recording: Automatic script recording =========================== This package provides a very handy and powerful Python script recording facility. This can be used to: - record all actions performed on a traits based UI into a *human readable*, Python script that should be able to recreate your UI actions. - easily learn the scripting API of an application. This package is not just a toy framework and is powerful enough to provide full script recording to the Mayavi_ application. Mayavi is a powerful 3D visualization tool that is part of ETS_. .. _Mayavi: http://code.enthought.com/projects/mayavi .. _ETS: http://code.enthought.com/projects/tool-suite.php .. _scripting-api: The scripting API ------------------ The scripting API primarily allows you to record UI actions for objects that have Traits. Technically the framework listens to all trait changes so will work outside a UI. We do not document the full API here, the best place to look for that is the ``apptools.scripting.recorder`` module which is reasonably well documented. We provide a high level overview of the library. The quickest way to get started is to look at a small example. .. _scripting-api-example: A tour by example ~~~~~~~~~~~~~~~~~~~ The following example is taken from the test suite. Consider a set of simple objects organized in a hierarchy:: from traits.api import (HasTraits, Float, Instance, Str, List, Bool, HasStrictTraits, Tuple, Range, TraitPrefixMap, Trait) from apptools.scripting.api import (Recorder, recordable, set_recorder) class Property(HasStrictTraits): color = Tuple(Range(0.0, 1.0), Range(0.0, 1.0), Range(0.0, 1.0)) opacity = Range(0.0, 1.0, 1.0) representation = Trait('surface', TraitPrefixMap({'surface':2, 'wireframe': 1, 'points': 0})) class Toy(HasTraits): color = Str type = Str # Note the use of the trait metadata to ignore this trait. ignore = Bool(False, record=False) class Child(HasTraits): name = Str('child') age = Float(10.0) # The recorder walks through sub-instances if they are marked # with record=True property = Instance(Property, (), record=True) toy = Instance(Toy, record=True) friends = List(Str) # The decorator records the method. @recordable def grow(self, x): """Increase age by x years.""" self.age += x class Parent(HasTraits): children = List(Child, record=True) recorder = Instance(Recorder, record=False) Using these simple classes we first create a simple object hierarchy as follows:: p = Parent() c = Child() t = Toy() c.toy = t p.children.append(c) Given this hierarchy, we'd like to be able to record a script. To do this we setup the recording infrastructure:: from mayavi.core.recorder import Recorder, set_recorder # Create a recorder. r = Recorder() # Set the global recorder so the decorator works. set_recorder(r) r.register(p) r.recording = True The key method here is the ``r.register(p)`` call above. It looks at the traits of ``p`` and finds all traits and nested objects that specify a ``record=True`` in their trait metadata (all methods starting and ending with ``_`` are ignored). All sub-objects are in turn registered with the recorder and so on. Callbacks are attached to traits changes and these are wired up to produce readable and executable code. The ``set_recorder(r)`` call is also very important and sets the global recorder so the framework listens to any functions that are decorated with the ``recordable`` decorator. Now lets test this out like so:: # The following will be recorded. c.name = 'Shiva' c.property.representation = 'w' c.property.opacity = 0.4 c.grow(1) To see what's been recorded do this:: print r.script This prints:: child = parent.children[0] child.name = 'Shiva' child.property.representation = 'wireframe' child.property.opacity = 0.40000000000000002 child.grow(1) The recorder internally maintains a mapping between objects and unique names for each object. It also stores the information about the location of a particular object in the object hierarchy. For example, the path to the ``Toy`` instance in the hierarchy above is ``parent.children[0].toy``. Since scripting with lists this way can be tedious, the recorder first instantiates the ``child``:: child = parent.children[0] Subsequent lines use the ``child`` attribute. The recorder always tries to instantiate the object referred to using its path information in this manner. To record a function or method call one must simply decorate the function/method with the ``recordable`` decorator. Nested recordable functions are not recorded and trait changes are also not recorded if done inside a recordable function. .. note:: 1. It is very important to note that the global recorder must be set via the ``set_recorder`` method. The ``recordable`` decorator relies on this being set to work. 2. The ``recordable`` decorator will work with plain Python classes and with functions too. To stop recording do this:: r.unregister(p) r.recording = False The ``r.unregister(p)`` reverses the ``r.register(p)`` call and unregisters all nested objects as well. .. _recorder-advanced-uses: Advanced use cases ~~~~~~~~~~~~~~~~~~~~ Here are a few advanced use cases. - The API also provides a ``RecorderWithUI`` class that provides a simple user interface that prints the recorded script and allows the user to save the script. - Sometimes it is not enough to just record trait changes, one may want to pass an arbitrary string or command when recording is occuring. To allow for this, if one defines a ``recorder`` trait on the object, it is set to the current recorder. One can then use this recorder to do whatever one wants. This is very convenient. - To ignore specific traits one must specify either a ``record=False`` metadata to the trait definition or specify a list of strings to the ``register`` method in the ``ignore`` keyword argument. - If you want to use a specific name for an object on the script you can pass the ``script_id`` parameter to the register function. For more details on the recorder itself we suggest reading the module source code. It is fairly well documented and with the above background should be enough to get you going. apptools-4.1.0/docs/Makefile0000644000175100001440000000607411674464005016744 0ustar ischnellusers00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/metasci.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/metasci.qhc" latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." apptools-4.1.0/image_LICENSE_OOo.txt0000644000175100001440000005761311674464005020122 0ustar ischnellusers00000000000000GNU Lesser General Public License Table of Contents * GNU Lesser General Public License * Preamble * Terms and Conditions for Copying, Distribution and Modification GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 (The master copy of this license lives on the GNU website.) Copyright (C) 1991, 1999 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, 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 this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: * a) The modified work must itself be a software library. * b) You must cause the files modified to carry prominent notices * stating that you changed the files and the date of any change. * c) You must cause the whole of the work to be licensed at no charge * to all third parties under the terms of this License. * d) If a facility in the modified Library refers to a function or a * table of data to be supplied by an application program that uses * the facility, other than as an argument passed when the facility is * invoked, then you must make a good faith effort to ensure that, in * the event an application does not supply such function or table, * the facility still operates, and performs whatever part of its * purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: * a) Accompany the work with the complete corresponding * machine-readable source code for the Library including whatever * changes were used in the work (which must be distributed under * Sections 1 and 2 above); and, if the work is an executable linked * with the Library, with the complete machine-readable "work that * uses the Library", as object code and/or source code, so that the * user can modify the Library and then relink to produce a modified * executable containing the modified Library. (It is understood that * the user who changes the contents of definitions files in the * Library will not necessarily be able to recompile the application * to use the modified definitions.) * b) Use a suitable shared library mechanism for linking with the * Library. A suitable mechanism is one that (1) uses at run time a * copy of the library already present on the user's computer system, * rather than copying library functions into the executable, and (2) * will operate properly with a modified version of the library, if * the user installs one, as long as the modified version is * interface-compatible with the version that the work was made with. * c) Accompany the work with a written offer, valid for at least * three years, to give the same user the materials specified in * Subsection 6a, above, for a charge no more than the cost of * performing this distribution. * d) If distribution of the work is made by offering access to copy * from a designated place, offer equivalent access to copy the above * specified materials from the same place. * e) Verify that the user has already received a copy of these * materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. 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 not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: * a) Accompany the combined library with a copy of the same work * based on the Library, uncombined with any other library facilities. * This must be distributed under the terms of the Sections above. * b) Give prominent notice with the combined library of the fact that * part of it is a work based on the Library, and explaining where to * find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the 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 specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS